18 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/list, prompt, cancel, streamed assistant/thought chunks, tool-call rendering (including Zed terminal cards), resumable session replay, slash commands, one-shot permission prompts, per-session model selection and permission presets, and session modes (the picker, via @deepseek-ai/dsh-plan-mode). The largest unbuilt areas are MCP passthrough and agent plans, 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, sessionCapabilities.list, and 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 | ✅ | ✅ | ✅ | A flattened prompt beginning with / dispatches through ctx.commands without a model request; ordinary input maps to agent.followup. One request is in flight per session. |
session/cancel |
S | ✅ | ✅ | ✅ | Aborts the exact direct command, or applies queue-aware agent.cancel and settles its prompt cancelled, scoped to one session. |
session/set_mode |
S | ✅ | ✅ | ✅ | Composed opportunistically: with @deepseek-ai/dsh-plan-mode mounted, session/new/session/load advertise the fixed default / plan projection and session/set_mode records the boolean pending intent (optimistic current_mode_update; logged plan/mode lands at the turn boundary). Without the plugin: no modes advertised, set_mode rejected (see §6 Modes). |
session/set_config_option |
S | ✅ | ✅ | ✅ | A provider/model select is present for a complete registered target; one permission select is added when ctx.permission is composed. Every response carries the complete refreshed state. |
| model selection | S | ✅ | ✅ | ✅ | No distinct stable session/set_model — model is the model-category session/set_config_option. Values preserve the provider/model pair, catalogs come from ctx.llm, selection is per session, and session/load restores the last requested pair. Codex also supports the legacy unstable_setSessionModel ext method. |
session/list |
S | ✅ | ✅ | ✅ | Uses live-preferred ctx.sessionQuery; returns absolute-cwd sessions newest-first with optional folded title and exact cwd filtering. Pagination is not emitted; supplied cursors are rejected. |
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 bridge answers the ctx.approval seam for the agents it owns: an ask from a hook/plugin becomes an editor prompt attached to the streamed tool call, one-shot allow_once/reject_once options only. Whether a call asks is policy (nothing asks by default); allow_always is deferred (grant storage). |
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 | ⚠️ | ✅ | ⚠️ | The bridge drives unstable_createElicitation for ask_user_question form prompts (session-scoped, no URL-mode flow yet). Claude calls the unstable_* elicitation methods for MCP server elicitations; Codex maps elicitations onto session/request_permission. |
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 | ⚠️ | ✅ | ✅ | list is advertised; delete/resume/close/additionalDirectories/fork remain off. |
auth.logout |
S | ❌ | ✅ | ✅ | Not advertised. |
authMethods[] |
S | ⚠️ | ✅ | ✅ | Advertised as empty (no auth required to reach the model). |
agentInfo (name/version) |
S | ✅ | ✅ | ✅ | Fixed literals: deepseek-harness-acp / 0.0.1 (not 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 appended tool/result via presentResult; replacement results rewrite model context and do not duplicate or overwrite execution presentation. |
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 | ✅ | ✅ | ✅ | Full effective snapshot after create/load and registry changes; names, descriptions, and unstructured-input hints come from ctx.commands. |
current_mode_update |
S | ✅ | ✅ | ✅ | Echoed optimistically on session/set_mode and re-notified when a logged plan/mode maps to a different wire id (covers the exit_plan_mode tool flipping the session back). |
config_option_update |
S | ❌ | ✅ | ✅ | Config options exist (advertised in session/new/session/load, switched via session/set_config_option), but the bridge never pushes agent-initiated changes — an operator default drift is narrated to the MODEL, not echoed to the editor. Future work in the sandbox Agent Note § Per-session mode switching. |
usage_update |
S | ❌ | ✅ | ✅ | Token/cost reporting not surfaced (the harness records token usage internally on assistant/message). |
session_info_update |
S | ✅ | ⚠️ | ⚠️ | Log-backed title events push title and event time; load replay uses the same mapping. |
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 Agent Note.
| Feature | Stable | Bridge | Claude | Codex | Notes |
|---|---|---|---|---|---|
ToolCallKind mapping |
S | ✅ | ✅ | ✅ | execute/read/edit declared by each tool's presentCall; presenter-less tools render other (no name sniffing); richer mapping possible. |
ToolCallStatus |
S | ✅ | ✅ | ✅ | in_progress → completed/failed. |
content blocks |
S | ✅ | ✅ | ✅ | Text content; the description renders above the card. |
diff content |
S | ✅ | ✅ | ✅ | The write/edit tools declare a diff render intent: presentCall → a call-time { card: 'diff' } snippet, and presentResult → a result-time { card: 'diff' }. For an edit or an overwrite it carries the applied hunk(s) with surrounding context (one per replace_all site), computed from the before/after text and persisted on the tool/result event as meta; for a create (no before-image) it is an args-derived whole-file diff. The bridge emits { type: 'diff', path, oldText, newText } content blocks; a successful mutation ALWAYS returns the result diff (an ACP tool_call_update.content replaces the call's content, so the result diff — not the model-facing text — is what survives). |
terminal content |
S | ✅ | ✅ | ✅ | Via the Zed _meta terminal convention (see below), not the spec terminal/* sub-protocol. |
locations (follow-along) |
S | ✅ | ✅ | ✅ | The read/write/edit tools emit { path, line? } file-location hints via presentCall. |
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
Session modes ✅ (the plan-mode Agent Note): ACP owns the fixed default / plan wire vocabulary and projects it onto ctx.planMode's boolean { active, pending? } state; session/set_mode calls set() and current_mode_update tracks the optimistic selection plus each distinct committed plan/mode flip. Config options ✅: the bridge advertises a model select from the advisory LLM provider/model catalog, preserving each provider/model pair in an opaque value and grouping multiple providers. A selected pair is isolated to one session, snapshotted with the prompt for each step, applied through agent/request, and restored from the logged request header on load. When ctx.permission is composed, the bridge also advertises one permission select whose values come from the deployment preset table and whose current value derives from the session log; idle permission switches anchor at the next agent/step inside its open turn. The division is picker-to-collaboration-state / knobs-to-config-options: individual environment knobs and the provider/model selector are not modes. See the model-catalog Agent Note and sandbox Agent Note.
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 Agent Note. |
| Disconnect / disposal teardown | S | ✅ | Quiesces every live session on client disconnect or Cordis disposal. |
_meta extensibility |
S | ⚠️ | Consumed for the Zed terminal cap and emitted for terminal cards. Listed sessions add deepseek-harness/sessionReference with a canonical URI when cross-session references are mounted. |
| Background-task ownership isolation | — | ✅ | Generic task_output/task_kill reject tasks whose branded owner SessionId belongs to another session. |
| 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:
- Session lifecycle —
session/delete, thensession/resume/session/close. - Agent plan (
sessionUpdate: 'plan') — surface the loop's plan as structured entries. - MCP passthrough (
mcpServersonsession/new+mcpCapabilities). - Richer prompt content — image / embedded
resourceblocks (needs a multimodal model path). - Usage reporting (
usage_update) — the harness already records token usage internally (onassistant/message). - 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:
README.md,src/index.ts, and the ACP Agent Notes under.agents/notes/.