feat(acp): implement the ACP v1 elicitation family (form mode) - #159
Open
YoungSx wants to merge 3 commits into
Open
feat(acp): implement the ACP v1 elicitation family (form mode)#159YoungSx wants to merge 3 commits into
YoungSx wants to merge 3 commits into
Conversation
Add the stable `elicitation/create` request and `elicitation/complete` notification, which the client previously answered with -32601 because the whole family was unimplemented. The private `_interaction.ask_user` extension covered the same scenario but is invisible to third-party agents, so a standards-compliant agent had no way to ask for structured input. Model the request as a tagged union on `mode` with form, url, and passthrough variants, mirroring the McpServer pattern: an unrecognized mode keeps its raw payload and round-trips byte for byte instead of being rendered as a known mode. The same applies to unknown property schema types and unknown multi-select item types. ACP requires `mode` explicitly and, unlike MCP, does not default an omitted mode to form, so an absent mode lands in the passthrough variant rather than being promoted. Advertise `elicitation.form` by default and leave `url` unadvertised: URL mode obliges the client to open the target in a context the agent's model cannot inspect, and that platform capability does not exist yet. Advertising it would invite OAuth flows this client cannot honour, and the spec forbids agents from falling back to form mode for those. Tracked separately for follow-up. Gate the family fail-closed at two levels. An omitted capability object means the method genuinely does not exist here, so it keeps answering -32601, symmetric with the fs and terminal gates. An advertised family with an unadvertised mode answers -32602, which the elicitation spec names explicitly because only the mode is unavailable. Carry the capability through the v2 initialize wire form too, where `elicitation` is a root capability unlike fs/terminal/session, so it is not silently dropped. Refs #146
Project inbound `elicitation/create` form requests into the same authoritative per-conversation pending-interaction chain that already owns ask_user, rather than adding a second state owner: the panel state coordinator stores them per conversation, conversation sync projects them, and session switch, conversation removal and remote reset clear them alongside ask_user. The composer is disabled while either interaction is pending. Build the form from the requested schema with one field view model per primitive type and validate locally before answering, so range, length, pattern and selection-count constraints surface next to the field the agent declared them on. Submitted content keeps each field's JSON type — an integer stays a number and a multi-select stays an array — because the agent re-validates the content against its own schema and a stringly-typed payload would fail that check. Offer decline and cancel next to submit: the spec requires clear decline and cancel controls, and an agent must not assume an elicitation succeeds. A field whose schema type this client does not model is skipped rather than rendered as some other control, matching the SDK's passthrough contract. Refs #146
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The formatting gate only inspects files a PR touches, so this pre-existing violation stayed latent until this branch modified ChatService.cs: `dotnet format` wants the two trailing comments indented into the preceding case body, which would misattribute a note about the deliberately unprojected variants to the `SessionInfoUpdate` arm. Put them on an explicit `default:` arm instead, which is where they belong semantically and is what the formatter already accepts.
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.
Closes #146. Follow-up for URL mode: #154.
What was missing
The stable ACP v1 elicitation family had zero implementation, so any
standards-compliant agent asking for structured input got
-32601 Method not found. The private_interaction.ask_userextension covered the samescenario, but it is invisible to third-party agents — functional, not
interoperable.
SDK
elicitation/create(request) andelicitation/complete(notification).clientCapabilities.elicitation, carried in both the v1 and v2 initializewire forms (
elicitationis a root capability in v2, unlikefs/terminal/session).
titled and untitled enums, formats, and range/length/pattern/count
constraints.
mode, unknown property schematype, and unknown multi-select itemtypes keep their raw payload and round-trip byte for byte instead of being
rendered as a known variant. ACP requires
modeexplicitly, so an omittedmode is not promoted to form the way MCP would.
Capability gating (fail-closed, two levels)
elicitationnot advertised-32601 Method not found-32602 Invalid params-32602 Invalid paramsURL mode is deliberately not advertised
URL mode obliges the client to open the target in a context the agent's model
cannot inspect, and that platform capability does not exist here yet.
Advertising it would invite OAuth flows this client cannot honour, and the spec
forbids agents from falling back to form mode for those. Tracked in #154.
Chat surface
Form requests join the existing authoritative per-conversation
pending-interaction chain that already owns ask_user — no second state owner.
Session switch, conversation removal, and remote reset clear them alongside
ask_user; the composer is disabled while either is pending.
Submitted content preserves each field's JSON type (integer stays a number,
multi-select stays an array) because the agent re-validates content against its
own schema. A field type the client does not model is skipped rather than
rendered as some other control.
Public API compatibility
The new
IAcpClientmembers ship as default interface implementations andClientCapabilities' published constructor signature is unchanged, so thepackage validates against the 1.0.0 baseline with no suppressions.
Verification
scripts/gates/run-acp-sdk-gates.sh(format, analyzers, tests, pack + ApiCompat vs 1.0.0)net10.0-desktop)Both fail-closed gates were confirmed with a temporary throw probe to prove the
tests actually reach the gating branch, not just assert a green result; the
probes were removed and the suites re-run.
Not verified locally: WASM target (the
wasm-toolsworkload is unavailable inthis environment) and Windows/MSIX-specific gates — both are left to CI.
🤖 Generated with Claude Code