- session/close: ⚠️→❌ (no handler; SDK dispatch returns method_not_found — disconnect/disposal teardown is not the per-session method) - Codex plan: ⚠️→✅ (CodexEventHandler.updatePlan emits the stable `plan` update; the plan-as-text note was stale) - Codex elicitation: ✅→⚠️ (maps onto session/request_permission; does not call elicitation/create|complete) - Overview: qualify the "both adapters ship" clause — neither drives the client terminal/* family and only Claude uses fs/* - Remove a stray </content> sentinel that rendered literally at EOF
15 KiB
ACP feature support checklist
A structured inventory of Agent Client Protocol (ACP) features and where the harness's ACP bridge (@deepseek-ai/dsh-acp) stands on each. The bridge exposes the harness agent as an ACP server (the agent side of an editor↔agent connection), so "supported" below means the bridge implements the agent's half — answering an agent method, advertising a capability, or calling a client method.
Scope
This tracks the stable ACP v1 surface (schema 1.14.0, schema/v1/schema.json) PLUS the unstable/draft features that the two reference adapters — claude-agent-acp (Claude Code) and codex-acp (OpenAI Codex) — actually ship. A purely-unstable feature that neither reference adapter uses is omitted (see Out of scope).
Legend: ✅ supported · ⚠️ partial / fallback · ❌ not yet · — n/a. The Stable column marks whether the feature is in the released v1 schema (S) or only the unstable schema (U). The Claude / Codex columns record whether each reference adapter ships it, as a maturity signal.
At a glance
The bridge implements the core prompt-turn loop for N concurrent sessions: initialize, session new/load, prompt, cancel, streamed assistant/thought chunks, tool-call rendering (including Zed terminal cards), and resumable session replay. The largest unbuilt areas are the permission gate (session/request_permission), MCP passthrough, session modes / config options / model selection, slash commands, and agent plans — all of which both reference adapters ship — plus the client filesystem and terminal method families (which the adapters mostly do NOT drive either — see rows 43-49). See Gap summary.
1. Agent methods (client → agent)
| Method | Stable | Bridge | Claude | Codex | Notes |
|---|---|---|---|---|---|
initialize |
S | ✅ | ✅ | ✅ | Negotiates PROTOCOL_VERSION; advertises loadSession + baseline prompt caps. Snapshots the Zed _meta.terminal_output client cap. |
authenticate |
S | ⚠️ | ✅ | ✅ | No-op stub; the bridge advertises no authMethods, so there is nothing to authenticate. |
logout |
S | ❌ | ✅ | ✅ | Gated by agentCapabilities.auth.logout; not advertised. |
session/new |
S | ✅ | ✅ | ✅ | Maps to agents.create; requires an absolute cwd (becomes the session workspace); rejects non-empty additionalDirectories / mcpServers. |
session/load |
S | ✅ | ✅ | ✅ | Maps to agents.resume + full event-log replay; validates persisted cwd before constructing the agent. |
session/resume |
S | ❌ | ✅ | ✅ | Reconnect WITHOUT replay; gated by sessionCapabilities.resume. Not advertised. |
session/close |
S | ❌ | ✅ | ✅ | No session/close handler — the SDK dispatch returns method_not_found. The bridge tears sessions down on client disconnect / Cordis disposal (cross-cutting, see §8), but that is not the on-demand per-session method. |
session/prompt |
S | ✅ | ✅ | ✅ | Maps to agent.send; one in-flight prompt per session; settles on the owning turn's end. |
session/cancel |
S | ✅ | ✅ | ✅ | Queue-aware agent.cancel; settles the in-flight prompt cancelled, scoped to the one session. |
session/set_mode |
S | ❌ | ✅ | ✅ | Session modes not modeled (see §6 Modes). |
session/set_config_option |
S | ❌ | ✅ | ✅ | Config options not modeled. |
| model selection | S | ❌ | ✅ | ✅ | No distinct stable session/set_model — model is the model-category session/set_config_option. The bridge fixes the model per-bridge via config; no runtime switch. Codex still uses the legacy unstable_setSessionModel ext method. |
session/list |
S | ❌ | ✅ | ✅ | Gated by sessionCapabilities.list. The harness HAS sessionPersistence.list() (used internally for load-cwd validation) but does not expose it over ACP. |
session/delete |
S | ❌ | ✅ | ✅ | Gated by sessionCapabilities.delete. |
session/fork |
U | ❌ | ✅ | ❌ | Claude ships unstable_forkSession; Codex does not. |
2. Client methods the agent CALLS (agent → client)
These are capabilities the bridge would drive on the editor. The harness runs tools in-process (its own dsh-bash executor, direct file I/O), so it does not yet delegate to the editor for any of these.
| Method | Stable | Bridge | Claude | Codex | Notes |
|---|---|---|---|---|---|
session/update |
S | ✅ | ✅ | ✅ | The bridge's primary output channel (see §4). |
session/request_permission |
S | ❌ | ✅ | ✅ | The biggest gap. Tools run with the executor's full authority; no user authorization round-trip. The agent→sessionId reverse map is already in place to route a future permission request. Tracked TODO(rfc010-permission-gate). |
fs/read_text_file |
S | ❌ | ✅ | ❌ | The harness reads files directly (it does not see the editor's unsaved buffer state). Claude delegates; Codex does not. |
fs/write_text_file |
S | ❌ | ✅ | ❌ | Same — direct writes, no editor delegation. |
terminal/create |
S | ❌ | ❌ | ❌ | Neither reference adapter drives the client terminal API either — both, like the bridge, render shell output as tool-call content + a _meta channel (see §5 Terminal). |
terminal/output |
S | ❌ | ❌ | ❌ | As above. |
terminal/wait_for_exit |
S | ❌ | ❌ | ❌ | As above. |
terminal/kill |
S | ❌ | ❌ | ❌ | As above. |
terminal/release |
S | ❌ | ❌ | ❌ | As above. |
elicitation/create · elicitation/complete |
U | ❌ | ✅ | ⚠️ | Structured user-input forms. Claude calls the unstable_* elicitation methods (to surface MCP server elicitations); Codex does NOT — its CodexElicitationHandler maps elicitations onto session/request_permission instead. |
3. Capabilities
3a. agentCapabilities (advertised by the bridge)
| Capability | Stable | Bridge | Claude | Codex | Notes |
|---|---|---|---|---|---|
loadSession |
S | ✅ | ✅ | ✅ | Advertised true; backs session/load. |
promptCapabilities.image |
S | ❌ | ✅ | ✅ | Bridge advertises image: false; image prompt blocks are rejected. |
promptCapabilities.audio |
S | ❌ | ❌ | ❌ | audio: false; neither adapter accepts audio either. |
promptCapabilities.embeddedContext |
S | ❌ | ✅ | ✅ | embeddedContext: false; embedded resource blocks rejected. |
mcpCapabilities.{http,sse} |
S | ❌ | ✅ | ⚠️ | No MCP passthrough; mcpServers is rejected. Claude advertises http+sse, Codex http only. |
sessionCapabilities.* |
S | ❌ | ✅ | ✅ | None advertised (list/delete/resume/close/additionalDirectories/fork all off). |
auth.logout |
S | ❌ | ✅ | ✅ | Not advertised. |
authMethods[] |
S | ⚠️ | ✅ | ✅ | Advertised as empty (no auth required to reach the model). |
agentInfo (name/version) |
S | ✅ | ✅ | ✅ | From agentName / agentVersion config. |
_meta custom caps |
S | ❌ | ✅ | — | E.g. Claude's claudeCode.promptQueueing. The bridge advertises no custom _meta. |
3b. clientCapabilities (consumed by the bridge)
| Capability | Stable | Bridge | Notes |
|---|---|---|---|
fs.{readTextFile,writeTextFile} |
S | ❌ | Not consulted (the bridge never calls fs/*). |
terminal |
S | ❌ | Not consulted; the bridge keys terminal rendering off the Zed _meta.terminal_output cap instead. |
_meta.terminal_output (Zed) |
S (_meta) |
✅ | Snapshotted per session at create/load; gates terminal-card rendering. |
4. session/update variants
sessionUpdate |
Stable | Bridge | Claude | Codex | Notes |
|---|---|---|---|---|---|
agent_message_chunk |
S | ✅ | ✅ | ✅ | From assistant/chunk text-delta. |
agent_thought_chunk |
S | ✅ | ✅ | ✅ | From assistant/chunk reasoning-delta. |
user_message_chunk |
S | ✅ | ✅ | ✅ | Emitted during session/load replay to reconstruct the user side. |
tool_call |
S | ✅ | ✅ | ✅ | Tool-owned presentation (presentCall); see §5. |
tool_call_update |
S | ✅ | ✅ | ✅ | From tool/result via presentResult. |
plan |
S | ❌ | ✅ | ✅ | No agent plan emitted. Both adapters emit real plan entries (Codex's CodexEventHandler.updatePlan maps turn/plan/updated → { sessionUpdate: 'plan', entries }). |
available_commands_update |
S | ❌ | ✅ | ✅ | No slash commands advertised. |
current_mode_update |
S | ❌ | ✅ | ✅ | No session modes. |
config_option_update |
S | ❌ | ✅ | ✅ | No config options. |
usage_update |
S | ❌ | ✅ | ✅ | Token/cost reporting not surfaced (the harness HAS usage events internally). |
session_info_update |
S | ❌ | ⚠️ | ⚠️ | Session title/metadata not pushed. |
5. Tool-call rendering
Tool-call presentation is owned by each tool (presentCall / presentResult on the dsh-tools definition), not special-cased in the bridge — see the terminal-and-tool-rendering RFC.
| Feature | Stable | Bridge | Claude | Codex | Notes |
|---|---|---|---|---|---|
ToolCallKind mapping |
S | ✅ | ✅ | ✅ | execute/read/edit/other inferred from the tool; richer mapping possible. |
ToolCallStatus |
S | ✅ | ✅ | ✅ | in_progress → completed/failed. |
content blocks |
S | ✅ | ✅ | ✅ | Text content; the description renders above the card. |
diff content |
S | ❌ | ✅ | ✅ | No structured diff rendering for edits (would need a diffing edit tool + presenter). |
terminal content |
S | ✅ | ✅ | ✅ | Via the Zed _meta terminal convention (see below), not the spec terminal/* sub-protocol. |
locations (follow-along) |
S | ❌ | ✅ | ✅ | No file-location hints emitted. |
rawInput |
S | ✅ | ⚠️ | ✅ | Parsed tool args surfaced as rawInput. |
rawOutput |
S | ❌ | ⚠️ | ✅ | Not emitted. |
Terminal rendering
⚠️ Implemented via the Zed _meta convention (terminal_info / terminal_output / terminal_exit), gated on the client advertising _meta.terminal_output — NOT the spec's terminal/create sub-protocol (which would make the editor execute the command, bypassing dsh-bash's sandbox / env-scrub / ownership / cwd). Both reference adapters take the same _meta approach. Live incremental streaming (terminal_output_delta, which Codex negotiates) is a follow-up — the bridge currently sends the full captured output once on the result.
6. Session modes / config options / models
❌ None modeled. Both reference adapters ship modes (Claude: a "plan" auto-mode; Codex: read-only / agent / agent-full-access mapping to its approval+sandbox policy), the newer config-option surface, and runtime model selection. The harness fixes the model per-bridge via AcpConfig.model. These are coupled to the unbuilt permission gate (a mode often selects an approval policy), so they are natural follow-ups to it.
7. Content blocks
| Block | Stable | In prompts | In updates | Notes |
|---|---|---|---|---|
text |
S | ✅ | ✅ | Baseline. |
resource_link |
S | ✅ | ⚠️ | Accepted in prompts and rendered into text (acpPromptToText); not emitted as a structured update block. |
image |
S | ❌ | ❌ | Rejected in prompts (promptCapabilities.image: false). |
audio |
S | ❌ | ❌ | Rejected. |
resource (embedded) |
S | ❌ | ❌ | Rejected (embeddedContext: false). |
The bridge rejects unsupported prompt blocks rather than silently dropping them (promptHasUnsupportedContent), per the "explicit over implicit" convention.
8. Cross-cutting
| Feature | Stable | Bridge | Notes |
|---|---|---|---|
StopReason mapping |
S | ✅ | turnEndToStopReason is total over harness turn-end reasons → end_turn/max_tokens/cancelled. |
| Multi-session (N per connection) | S | ✅ | Strict per-session demux; concurrent streams never interleave. See the multi-session RFC. |
| Disconnect / disposal teardown | S | ✅ | Quiesces every live session on client disconnect or Cordis disposal. |
_meta extensibility |
S | ⚠️ | Consumed (Zed terminal cap) and emitted (terminal _meta); no other custom extensions. |
| Background-task ownership isolation | — | ✅ | bash_output/bash_kill reject another session's task via an opaque owner token. |
| stdout-is-the-protocol guarantee | S | ✅ | The bridge runs in an example with no stdout logger. |
Gap summary
Ranked by how commonly the reference adapters ship them and how much UX they unlock:
- Permission gate —
session/request_permission+ permission options. TrackedTODO(rfc010-permission-gate); the reverse map is already wired. Foundational, and a prerequisite for modes. - Session lifecycle —
session/list+session/delete(the persistence layer already lists), thensession/resume/session/close. - Modes / config options / model selection — coupled to the permission gate.
- Agent plan (
sessionUpdate: 'plan') — surface the loop's plan as structured entries. - Slash commands (
available_commands_update). - MCP passthrough (
mcpServersonsession/new+mcpCapabilities). - Richer prompt content — image / embedded
resourceblocks (needs a multimodal model path). - Diff + location tool rendering —
diffcontent andlocationsfor edit tools. - Usage reporting (
usage_update) — the harness already has the internal usage events. - Editor filesystem delegation (
fs/read_text_file/fs/write_text_file) — lets the agent see unsaved buffers; lower priority since the harness has direct disk access.
Out of scope
Unstable/draft ACP features that neither reference adapter ships are not tracked above: providers/* (LLM provider selection), mcp/connect·mcp/message·mcp/disconnect (client-side MCP passthrough), nes/* (Next Edit Suggestion), document/did* (LSP-style document sync), the v2 plan model (plan_update / plan_removed), boolean config options, $/cancel_request, and the draft Streamable-HTTP transport. They can be added if a target editor adopts them.
Sources
- Stable spec:
schema/v1/schema.json(schema1.14.0) anddocs/protocol/v1/*.mdxin the agent-client-protocol repo. - Reference adapters:
claude-agent-acpandcodex-acp. - Bridge:
packages/acp/README.md,packages/acp/src/index.ts, and the ACP RFCs underdocs/rfc/.