Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,60 @@ jobs:
- name: Test Go module
run: go test ./...

dotnet:
runs-on: ubuntu-latest
defaults:
run:
working-directory: clients/dotnet
steps:
- uses: actions/checkout@v6

- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm

# The .NET 10 SDK builds the net8.0 target and understands the .slnx
# solution format; the 8.0.x entry provides the net8.0 runtime so the
# net8.0 tests run natively.
- uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
10.0.x

- name: Install Node deps
working-directory: .
run: npm ci

# Verify the committed C# sources are in sync with the TypeScript
# protocol definitions. `git status --porcelain` (like the Kotlin / Go
# jobs) so a newly-emitted file also fails the check.
- name: Verify generated .NET is up to date
working-directory: .
run: |
npm run generate:dotnet
if [ -n "$(git status --porcelain -- clients/dotnet)" ]; then
echo "::error::Generated .NET sources are out of date. Run 'npm run generate:dotnet' and commit the result."
git status --porcelain -- clients/dotnet
git --no-pager diff -- clients/dotnet
exit 1
fi

- name: Restore .NET solution
run: dotnet restore

# Whitespace formatting gate — the C# analog of the Go job's gofmt check,
# governed by clients/dotnet/.editorconfig.
- name: Verify .NET formatting
run: dotnet format whitespace --verify-no-changes --no-restore

- name: Build .NET solution
run: dotnet build --no-restore --configuration Release

- name: Test .NET solution
run: dotnet test --no-build --configuration Release

- name: Pack .NET solution
run: dotnet pack --no-build --configuration Release

8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

Cross-cutting rules for AI coding agents working in this repository. Per-client
codegen conventions are in `clients/kotlin/AGENTS.md`,
`clients/swift/AGENTS.md`, and `clients/go/AGENTS.md`. Editorial rules
`clients/swift/AGENTS.md`, `clients/go/AGENTS.md`, and
`clients/dotnet/AGENTS.md`. Editorial rules
for protocol types are in
`.github/instructions/general-instructions.instructions.md`. Release mechanics
are in [`RELEASING.md`](RELEASING.md).

## Updating CHANGELOGs

This repo ships six independently-versioned artifacts (the spec plus
the Rust / Kotlin / Swift / TypeScript / Go clients), each with its
This repo ships seven independently-versioned artifacts (the spec plus
the Rust / Kotlin / Swift / TypeScript / Go / .NET clients), each with its
own `CHANGELOG.md` in Keep-a-Changelog format. The publish workflows
refuse to release a tag whose matching `## [X.Y.Z]` heading is
missing, so every user-visible change should land its CHANGELOG bullet
Expand Down Expand Up @@ -50,6 +51,7 @@ Map source paths to changelogs:
| `clients/swift/**` (non-generated) | `clients/swift/CHANGELOG.md` only. |
| `clients/typescript/**` (non-generated) | `clients/typescript/CHANGELOG.md` only. |
| `clients/go/**` (non-generated) | `clients/go/CHANGELOG.md` only. |
| `clients/dotnet/**` (non-generated) | `clients/dotnet/CHANGELOG.md` only. |
| `schema/**` | Root `CHANGELOG.md` (the schema is a spec output). |
| `scripts/generate*.ts` that changes any client's generated output | Every affected client's `CHANGELOG.md`. |

Expand Down
9 changes: 7 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ against them.
| `clients/kotlin/` | Kotlin/JVM library (`com.microsoft.agenthostprotocol:agent-host-protocol`). |
| `clients/swift/` | Swift package (consumed by SwiftPM at the repo root). |
| `clients/typescript/` | npm package `@microsoft/agent-host-protocol`. |
| `clients/go/` | Go module (`ahptypes`, `ahp`, `ahpws`). |
| `clients/dotnet/` | .NET / NuGet packages (`Microsoft.AgentHostProtocol`, `.Abstractions`, `.WebSockets`). |
| `.github/workflows/` | CI and per-artifact publish pipelines. |

## Local dev loop
Expand All @@ -42,6 +44,8 @@ cd clients/typescript && npm ci && npm test && npm run build
cd clients/rust && cargo test --workspace
cd clients/kotlin && ./gradlew build
swift build && swift test # Swift uses the root Package.swift
cd clients/go && go test ./...
cd clients/dotnet && dotnet test
```

## Releases
Expand All @@ -53,7 +57,7 @@ see [`docs/specification/versioning.md`](docs/specification/versioning.md).

## Updating CHANGELOGs

This repo ships five independently-versioned artifacts (spec + four clients),
This repo ships seven independently-versioned artifacts (spec + six clients),
each with its own `CHANGELOG.md` in [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
format. The publish workflows refuse to release a tag whose matching
`## [X.Y.Z]` heading is missing, so every user-visible change should land its
Expand Down Expand Up @@ -94,4 +98,5 @@ When iterating on the protocol surface in `types/`, see
for the project's editorial rules on type changes.

For language-specific code-gen conventions, see the `AGENTS.md` file in each
client directory (`clients/kotlin/AGENTS.md`, `clients/swift/AGENTS.md`).
client directory (`clients/go/AGENTS.md`, `clients/kotlin/AGENTS.md`,
`clients/swift/AGENTS.md`, `clients/dotnet/AGENTS.md`).
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ The Agent Host Protocol (AHP) defines how a portable, standalone sessions server
- **Kotlin** — Add `com.microsoft.agenthostprotocol:agent-host-protocol` from Maven Central to use from Android or any JVM project. See [`clients/kotlin/`](clients/kotlin/) for the source and [`CHANGELOG`](clients/kotlin/CHANGELOG.md). Released via `kotlin/vX.Y.Z` tags.
- **TypeScript** — Install `@microsoft/agent-host-protocol` to use the wire types, reducers, `AhpClient`, and the `WebSocketTransport`. See [`clients/typescript/`](clients/typescript/) and [`CHANGELOG`](clients/typescript/CHANGELOG.md). Released via `typescript/vX.Y.Z` tags; the Azure DevOps publish pipeline at [`clients/typescript/pipeline.yml`](clients/typescript/pipeline.yml) picks up the tag, validates it, and publishes to npm.
- **Go** — `go get github.com/microsoft/agent-host-protocol/clients/go` to use the `ahptypes` wire types, the `ahp` async client (client + pure reducers + pluggable `Transport`), and the `ahpws` WebSocket transport. See [`clients/go/`](clients/go/) and [`CHANGELOG`](clients/go/CHANGELOG.md). Released via `clients/go/vX.Y.Z` tags — the Go module proxy indexes the directory-prefixed tag directly from this repo, so there is no separate package registry.
- **.NET** — Install `Microsoft.AgentHostProtocol` (and `Microsoft.AgentHostProtocol.WebSockets` for a `ClientWebSocket` transport) to use the wire types, the pure reducers, the async `AhpClient`, and the `MultiHostClient`. The `Microsoft.AgentHostProtocol.Abstractions` package carries the wire types + transport/serializer interfaces alone. See [`clients/dotnet/`](clients/dotnet/) and [`CHANGELOG`](clients/dotnet/CHANGELOG.md). Released to NuGet.org via `dotnet/vX.Y.Z` tags.
- **[AHPX](https://github.com/TylerLeonhardt/ahpx)** — A command-line and Node.js client for connecting to AHP servers, managing sessions, and sending prompts.
- **[VS Code](https://github.com/microsoft/vscode)** — VS Code includes Agent Sessions client code for working with AHP hosts.

### Servers

- **[VS Code agent host](https://github.com/microsoft/vscode)** — The reference AHP server implementation. Start in [`src/vs/platform/agentHost/node/`](https://github.com/microsoft/vscode/tree/main/src/vs/platform/agentHost/node) when browsing the repository.

For consumers that need to talk to two or more hosts at once, the Rust SDK ships a `MultiHostClient` abstraction in [`ahp::hosts`](https://docs.rs/ahp/latest/ahp/hosts/), the Swift SDK ships `MultiHostClient` in `AgentHostProtocolClient`, and the Go SDK ships `MultiHostClient` in [`ahp/hosts`](clients/go/ahp/hosts/). Single-host consumers use the same API via `MultiHostClient::single` in Rust, `MultiHostClient.single(...)` in Swift, or `hosts.Single(...)` in Go. See [Connecting to Multiple Hosts](https://microsoft.github.io/agent-host-protocol/guide/clients-multi-host) for the design and surface.
For consumers that need to talk to two or more hosts at once, the Rust SDK ships a `MultiHostClient` abstraction in [`ahp::hosts`](https://docs.rs/ahp/latest/ahp/hosts/), the Swift SDK ships `MultiHostClient` in `AgentHostProtocolClient`, the Go SDK ships `MultiHostClient` in [`ahp/hosts`](clients/go/ahp/hosts/), and the .NET SDK ships `MultiHostClient` in `Microsoft.AgentHostProtocol.Hosts`. Single-host consumers use the same API via `MultiHostClient::single` in Rust, `MultiHostClient.single(...)` in Swift, `hosts.Single(...)` in Go, or `MultiHostClient.SingleAsync(...)` in .NET. See [Connecting to Multiple Hosts](https://microsoft.github.io/agent-host-protocol/guide/clients-multi-host) for the design and surface.

## Versioning and releases

Expand Down
21 changes: 21 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and a checked-in `clients/<lang>/release-metadata.json`.
| TypeScript | `typescript/vX.Y.Z` | `clients/typescript/pipeline.yml` (Azure DevOps) | npm (`@microsoft/agent-host-protocol`) via ESRP. |
| Swift | `vX.Y.Z` (bare) | `.github/workflows/publish-swift.yml` | SwiftPM resolves the tag directly. |
| Go | `clients/go/vX.Y.Z` | `.github/workflows/publish-go.yml` | Go module proxy resolves the tag directly. |
| .NET | `dotnet/vX.Y.Z` | maintainer-owned pipeline (see below) | NuGet.org (`Microsoft.AgentHostProtocol`, `.Abstractions`, `.WebSockets`). |

> **Why Swift gets the bare semver tag namespace:** SwiftPM only resolves
> packages by matching plain `X.Y.Z` / `vX.Y.Z` git tags at the manifest's
Expand Down Expand Up @@ -146,6 +147,26 @@ trigger started the run.
`go get github.com/microsoft/agent-host-protocol/clients/go@vX.Y.Z`;
no registry push happens.

### .NET (`dotnet/vX.Y.Z`)

1. Update `clients/dotnet/VERSION` to the new bare semver string (no
leading `v`).
2. Run `npm run generate:metadata` and commit the regenerated
`clients/dotnet/release-metadata.json`.
3. Rotate `clients/dotnet/CHANGELOG.md`.
4. Merge to `main`.
5. Tag: `git tag dotnet/v0.X.Y && git push origin dotnet/v0.X.Y`.
6. Publish the libraries (`Microsoft.AgentHostProtocol`,
`Microsoft.AgentHostProtocol.Abstractions`,
`Microsoft.AgentHostProtocol.WebSockets`) to NuGet.org. This client does
not ship its own publish automation — the maintainers wire the
`dotnet pack` + `dotnet nuget push` step into their own release pipeline,
the same way the Kotlin and TypeScript packages publish through the signed
Azure DevOps / ESRP pipelines rather than a GitHub Actions registry push.
The per-PR CI job already builds, tests, and runs the test-parity gate for
the solution; `npm run verify:changelog` guards the
`clients/dotnet/VERSION` ↔ `CHANGELOG.md` heading match.

### Spec (`spec/vX.Y.Z`)

1. Bump `PROTOCOL_VERSION` in `types/version/registry.ts` (and, if the
Expand Down
118 changes: 118 additions & 0 deletions clients/dotnet/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Formatting + code-style conventions for the .NET client. Whitespace is gated
# in CI by `dotnet format whitespace --verify-no-changes` (the C# analog of the
# Go lane's `gofmt -l` check); the code-style (IDExxxx) and analyzer (CAxxxx)
# rules below run at build time via <EnforceCodeStyleInBuild> +
# <EnableNETAnalyzers> and are gated by <TreatWarningsAsErrors>.
root = true

[*.cs]
indent_style = space
indent_size = 4
tab_width = 4
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8

# ── C# code style (mirrors what the hand-written code already does) ──────────

# File-scoped namespaces (25/25 src files use them; zero block-scoped). The code
# follows this uniformly, so it is enforced (build error via TreatWarningsAsErrors).
csharp_style_namespace_declarations = file_scoped:warning

# `var` is the house style: heavily used where the type is apparent or named on
# the right, but the code does keep explicit types in some spots, so this is a
# documented preference (suggestion), not an enforced rule that would demand
# reformatting the explicit-type sites.
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion

# Members are referenced unqualified (no `this.` / no type-name qualification).
# The code follows this uniformly, so it is enforced.
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_event = false:warning

# Brace control-flow bodies. The code mixes braced and single-line forms, so
# this is a documented preference (suggestion) rather than an enforced rule.
csharp_prefer_braces = true:suggestion

# Modern expression preferences. The pattern-matching / initializer rules below
# the code already follows uniformly (enforced); the expression-bodied and
# simple-using preferences are documented (silent) since usage is mixed.
csharp_style_expression_bodied_methods = true:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
csharp_style_pattern_matching_over_as_with_null_check = true:warning
dotnet_style_object_initializer = true:warning
dotnet_style_collection_initializer = true:warning
csharp_prefer_simple_using_statement = true:silent

# ── Code-analysis (CAxxxx) severities ───────────────────────────────────────

# Dispose-reliability rules promoted to error: a type owning a disposable field
# must be disposable (CA1001), disposable fields must be disposed (CA2213), and
# objects must be disposed before going out of scope (CA2000). These are the
# bounded high-value rules that catch the undisposed-CTS/semaphore class of leak
# without pulling in the broader AnalysisMode=Recommended set.
dotnet_diagnostic.CA1001.severity = error
dotnet_diagnostic.CA2213.severity = error
dotnet_diagnostic.CA2000.severity = error

# ── Async / threading (VSTHRDxxxx) severities ───────────────────────────────

# The Microsoft.VisualStudio.Threading analyzers stay enabled for their
# high-value rule: VSTHRD002 (synchronously blocking on async via .Result /
# .Wait() / .GetAwaiter().GetResult()) keeps its default error severity — that is
# the genuinely deadlock-prone pattern, and the code is clean of it.
#
# Two rules are method-name / heuristic based and fire only on verified-correct
# patterns here, so they are downgraded with the specific reason each is a false
# positive:
#
# VSTHRD103 (call async methods when in an async method) matches by method NAME on
# (a) MemoryStream.Write — an in-memory buffer copy, not I/O; (b) a fire-and-forget
# CancellationTokenSource.Cancel() releasing a linked-token waiter, where
# CancelAsync's await-the-callbacks semantics are unnecessary; (c) the synchronous
# AhpClient.Connect factory, which does no I/O (the transport is already connected;
# the handshake is the separate async InitializeAsync). None are sync-over-async.
dotnet_diagnostic.VSTHRD103.severity = suggestion
#
# VSTHRD003 (avoid awaiting foreign Tasks) warns of a deadlock that needs
# synchronization-context capture; every await in this library passes
# ConfigureAwait(false), which prevents capture, so the awaited
# TaskCompletionSource / SemaphoreSlim.WaitAsync tasks cannot deadlock.
dotnet_diagnostic.VSTHRD003.severity = suggestion

# ── Test / example relaxations ────────────────────────────────────────────────

# Test method names use Method_Scenario underscores by deliberate convention
# (mirrors the Swift/Go test naming); CA1707 (no underscores in identifiers)
# does not apply to test code. The dispose rules above are a shipping-library
# guard: test bodies legitimately new-up short-lived disposables (transports,
# listeners, clients, stores) that the test runner / GC reclaim, so CA2000 /
# CA1001 / CA2213 are not enforced as errors there.
# CA2016: test infrastructure uses `Task.Run(() => SomeAsync(ct))` for
# fire-and-forget background helpers where the lambda already forwards `ct` to
# the actual async work; passing ct to Task.Run itself would cancel the task
# scheduling, which is wrong here. 54 sites across FakeHost / MultiHostClientTests.
# CA1861: inline `new[] { ... }` literals are idiomatic in single-call assertions
# and the per-call allocation cost in tests is irrelevant.
[tests/**/*.cs]
dotnet_diagnostic.CA1707.severity = none
dotnet_diagnostic.CA2000.severity = none
dotnet_diagnostic.CA1001.severity = none
dotnet_diagnostic.CA2213.severity = none
dotnet_diagnostic.CA2016.severity = none
dotnet_diagnostic.CA1861.severity = none
# VSTHRD200 (Async naming convention) stays at error for the shipping libraries;
# test helper methods are exempt, like the CA1707 underscore-naming relaxation.
dotnet_diagnostic.VSTHRD200.severity = none

# Examples are illustrative end-to-end snippets, not shipping code; the dispose
# guard is likewise not enforced there.
[examples/**/*.cs]
dotnet_diagnostic.CA2000.severity = none
dotnet_diagnostic.CA1001.severity = none
dotnet_diagnostic.CA2213.severity = none
2 changes: 2 additions & 0 deletions clients/dotnet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bin/
obj/
Loading