Skip to content

Add a first-party .NET client - #206

Merged
Connor Peet (connor4312) merged 3 commits into
microsoft:mainfrom
joshmouch:pr/dotnet-client
Aug 23, 2026
Merged

Add a first-party .NET client#206
Connor Peet (connor4312) merged 3 commits into
microsoft:mainfrom
joshmouch:pr/dotnet-client

Conversation

@joshmouch

@joshmouch Josh (joshmouch) commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

A first-party .NET / NuGet client for AHP 1.0, at cross-language parity with the Rust, Kotlin, Swift, TypeScript, and Go clients.

What’s included

  • Microsoft.AgentHostProtocol.Abstractions: generated wire types plus transport and serializer interfaces.
  • Microsoft.AgentHostProtocol: reducers, AhpClient, integrated WebSocketTransport, and MultiHostClient.
  • Multi-targeted shipping libraries for netstandard2.0 and net8.0; tests and examples run on net8.0.
  • C# code generation from the canonical TypeScript protocol types, wired into generated-source freshness checks.
  • Shared reducer and round-trip fixture coverage, including the AHP 1.0 automation channels.
  • CI build, test, format, pack, changelog-fragment, and release-metadata integration.

Validation

  • 496 .NET tests pass with no skips.
  • Both NuGet packages build for netstandard2.0 and net8.0 and pack successfully.
  • Repository typecheck, lint, metadata, changelog, generated-source, and protocol tests pass.

Ownership and publishing

I’m happy to continue maintaining the .NET client and keep it in protocol parity. Package IDs remain Microsoft.AgentHostProtocol.*; publishing can be wired to the maintainers’ signed NuGet pipeline.

@joshmouch
Josh (joshmouch) force-pushed the pr/dotnet-client branch 6 times, most recently from b72eeed to ca6e1a3 Compare June 10, 2026 14:07
@joshmouch
Josh (joshmouch) force-pushed the pr/dotnet-client branch 4 times, most recently from ef382ab to 5cc1e43 Compare June 16, 2026 08:13
@joshmouch
Josh (joshmouch) force-pushed the pr/dotnet-client branch 9 times, most recently from 3c206df to 422ff6b Compare June 18, 2026 04:29
@joshmouch
Josh (joshmouch) force-pushed the pr/dotnet-client branch 6 times, most recently from 4b290c9 to 2f97415 Compare June 25, 2026 13:12
@joshmouch
Josh (joshmouch) force-pushed the pr/dotnet-client branch 2 times, most recently from a2dc900 to 7fe7ca0 Compare June 30, 2026 13:11
@joshmouch
Josh (joshmouch) force-pushed the pr/dotnet-client branch 5 times, most recently from 8df6deb to 21ed4b0 Compare July 15, 2026 13:34
@joshmouch
Josh (joshmouch) force-pushed the pr/dotnet-client branch 2 times, most recently from 857d205 to fdc1ed6 Compare July 19, 2026 15:10
@joshmouch
Josh (joshmouch) force-pushed the pr/dotnet-client branch 2 times, most recently from 7b20e6f to 5f623fb Compare August 9, 2026 20:13
@wiktork

Copy link
Copy Markdown
Member

Josh (@joshmouch) are you still interested in making this contribution? Our team has interest here and the AHP folks encouraged us to start with your changes.

  • The current PR would need some updates to the protocol.
  • We have a requirement around targeting netstandard2.0.

Josh (joshmouch) and others added 2 commits August 21, 2026 15:27
First-party .NET client for the Agent Host Protocol: a hand-written JSON-RPC
client + multi-host runtime over a pluggable ITransport (WebSocket transport
included), generated wire types, and DI integration.

Includes OpenTelemetry-native self-instrumentation (a single ActivitySource +
Meter named from the generated AhpTelemetryNames holder), the host-* dropped-
event stream values from the shared telemetry contract, reconnect-supervisor
metrics, an OtelExport example, and recorder-based telemetry tests.
Add netstandard2.0 support, current generated protocol types and reducers, and complete CI and release-tool integration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@davidfowl David Fowler (davidfowl) changed the title Add a first-party .NET client (proposal) Add a first-party .NET client Aug 21, 2026
@davidfowl
David Fowler (davidfowl) marked this pull request as ready for review August 21, 2026 22:59
Comment thread clients/dotnet/src/AgentHostProtocol/WebSocketTransport.cs
Comment thread clients/dotnet/src/AgentHostProtocol/AhpClient.cs Outdated
Fold WebSocketTransport into the core package and propagate client-lifetime cancellation to background transport operations.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@davidfowl

Copy link
Copy Markdown
Collaborator

Security follow-up on \FileClientIdStore: the temporary file currently receives the client ID before its Unix permissions are tightened to \

@connor4312
Connor Peet (connor4312) merged commit 525f67f into microsoft:main Aug 23, 2026
9 checks passed
@joshmouch

Copy link
Copy Markdown
Contributor Author

Thanks Wiktor Kopec (@wiktork) — and yes, absolutely still interested! Genuinely thrilled this ended up being the starting point for your team; honestly that's the best outcome I could have hoped for here. Apologies for the slow reply.

Both of your points are covered now, thanks to David Fowler (@davidfowl):

  • netstandard2.0 — already good: the client multi-targets netstandard2.0;net8.0, and Harden .NET client security, reconnects, and Native AOT #411 keeps that split intact even on the security-sensitive paths (native open(2)/fchmod on the netstandard2.0 leg, where FileStreamOptions.UnixCreateMode isn't available).
  • protocol updates — the 1.0 adoption landed with the merge.

David Fowler (@davidfowl)#411 is a great read, and the FileClientIdStore catch is a sharp one. While going through it I spotted the same issue in the Swift client, which the .NET store was ported from: store() writes the id to the final path and only chmods to 0600 afterwards, so the first store for a given host id sits at 0644 in between. The store's own 0700 directory hardening doesn't cover it either — ensureDirectory() only applies it on the branch where it creates the directory, so a pre-existing app-support or XDG path keeps its own mode. Worth flagging that neither client's test would catch this: both assert the mode after store() returns, so they're green either way.

Written up separately rather than widening #411 — happy to open the Swift PR if that's useful.

Really glad to keep helping here — this has been a genuinely fun one to work on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants