feat: proxy primitive and public JSON-RPC peer layer#219
Open
chazcb wants to merge 3 commits into
Open
Conversation
ACP intermediaries — session routers, authz gates, audit taps — currently have to reimplement request/response correlation, cancellation propagation, and error passthrough because the SDK's JSON-RPC layer is private and there is no relay primitive. This exports the existing Connection/handler-chain layer, adds proxy() (two Connections wired back to back with forwarding fallbacks, mirroring the Rust SDK's Proxy role), exposes the in-memory stream pair, and lets fluent apps accept raw handlers on connect() so message-level middleware composes with typed apps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extracts linkClosed so all three paired-connection sites (proxy and both in-memory app connects) propagate the close reason instead of two of them dropping it; narrows openStreamConnection to the one option it uses; hoists the proxy forwarder to module level; replaces the proxy star re-export with named exports so future additions to proxy.ts don't silently widen the public API. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y role Renames the interception lists to fromClient/fromAgent to match how the Rust SDK's Proxy role frames interception (on_receive_request_from(Client), from(Agent)) so the two SDKs teach the same mental model, and adds a runnable snoop-proxy example plus README pointers so proxies are discoverable the same way agents and clients are. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a
proxy()primitive — the TypeScript counterpart of the Rust SDK'sProxyrole — and opens up the building blocks it stands on:proxy({ client, agent, fromClient, fromAgent })(src/proxy.ts): a protocol-terminating relay between a client stream and an agent stream. Each side is a full JSON-RPC connection, so forwarded requests are re-issued with the proxy's own ids and correlated back automatically, in both directions. Unhandled messages forward by default;fromClient/fromAgenthandlers can observe, rewrite (Handled.no(message)), answer (respond +Handled.yes()), or drop traffic before it crosses.Connection,ConnectionBuilder,Handled,JsonRpcHandler,IncomingMessage, message guards, etc. from the package root.Connection's own docs already describe it as the layer "for building generic JSON-RPC middleware or custom dispatch behavior" — this makes that usable outside the package.handlersoption on fluentconnect():agent(...)/client(...)apps can prepend raw message-level handlers per connection (logging, authorization, parameter rewriting) without changing their typed registrations. This is thin plumbing onto the pre-existingConnectionOptions.handlersmechanism.inMemoryStreamPair(): exports the internal helper so in-process endpoints (and proxy sides) can be wired without a transport.linkClosed(a, b): small internal helper deduplicating cross-close wiring; paired connections now propagate the close reason, so pending requests on the surviving side reject with the true cause.src/examples/proxy.ts): a runnable snoop proxy that wraps any agent command and logs traffic in both directions — point Zed at it exactly as you would the agent itself.Why
ACP intermediaries — session routers, authorization gates, audit taps, message transformers — currently have to reimplement request/response correlation, cancellation propagation, and error passthrough from raw streams, because the SDK's JSON-RPC layer is private and there is no relay primitive. The Rust SDK already ships this concept as the
Proxyrole with default forwarding and per-peer interception (on_receive_request_from(Client | Agent, ...)), documented in itsconcepts::proxiesmodule. This PR brings the TypeScript SDK to parity with the same mental model and naming (fromClient/fromAgent), adapted to TS conventions.Forwarding preserves observable protocol behavior end to end (covered by tests):
$/cancel_requestpropagates to the side handling the request; the eventual response still settles the original requestNotes for reviewers
_proxy/successorenvelope protocol is deliberately not included: it exists to chain proxy processes over a single pipe. This proxy owns a real stream per side, so proxies chain by connecting one proxy's agent stream to the next proxy's client stream. A conductor/P-ACP interop layer can build on this later../experimental/*subpath convention for new API (e.g.@agentclientprotocol/sdk/experimental/proxy), happy to restructure.