Skip to content

Added ACP support... - #38

Merged
sanchitrk merged 31 commits into
devfrom
feature/acp-support
Sep 30, 2026
Merged

sanchitrk merged 31 commits into
devfrom
feature/acp-support

Conversation

@prem315

@prem315 prem315 commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

feat: implement Agent Client Protocol (ACP) server and stdio CLI command

prem315 and others added 30 commits September 24, 2026 16:52
- Stream historical user messages, thoughts, assistant responses, and tool calls
  via session/update before completing session/load
- Fixes issue where clicking an existing thread caused Zed to drop it from the UI
Resolve the packages/codework/package.json conflict by taking dev's
dependency set and adding @agentclientprotocol/sdk on top. Drop the
duplicate @codeworksh/aikit/modelgen alias the auto-merge produced in
vite.config.ts and regenerate pnpm-lock.yaml with pnpm install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK3FBTLS7f7EkHJPMzA1uv
Review of the ACP server (PR #38): merge resolution, verification
results, a live stdio test against OpenRouter, findings in the ACP
layer, and the harness core gaps that block further ACP support.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK3FBTLS7f7EkHJPMzA1uv
- ToolStarted carries the call's arguments and the tool's optional label,
  so clients can show what a tool is doing as soon as it starts (COD-85).
- A configured plugin that is not installed now also publishes
  plugin.updated (status "failed") with the message, declaring file and
  session, so clients can show the advice to the user (COD-90).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace @agentclientprotocol/sdk with @codeworksh/acp, vendored from
t3code's effect-acp (pingdotgg/t3code#13784 @ f56e8b3815) under vendors/acp
and bundled into the CLI. Upstream's agent speaks ACP v2 only, so
vendors/acp/src/agent.ts is a local ACP v1 agent over the vendored
transport and v1 schemas; see vendors/acp/VENDOR.md.

codework acp is rewritten on it:
- prompts answer end_turn, cancelled, max_tokens, or a JSON-RPC error
  carrying the provider's message, from the harness execution events
- typed errors: unknown session -32002; invalid config values and
  unsupported prompt content -32602
- session/list matches the project exactly and pages with a cursor
- typed live feed and session/load replay; tool calls open with their
  command or label and input; plugin load failures shown to the user
- logs go to stderr so stdout carries only the protocol

E2E tests spawn the CLI and drive it with the vendored client: a protocol
test, and a live OpenRouter test (streaming, bash tool, cancel, provider
failure, replay after restart, uninstalled-plugin notice).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An untitled session is named by the first prompt it answers: the first
non-empty line, whitespace collapsed, cut on a word boundary to 48
characters. The promoted-prompt projection retitles in the same commit
that appends the prompt, and only replaces the default title, so an
explicit title is never overwritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ACP and `codework run` create sessions without a placeholder title so
the harness names them from their first prompt. ACP sends a
session_info_update when a prompt changes the title.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Callers that need to know whether a provider has an API key in the
environment use the same lookup aikit applies when it opens a provider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ModelCatalog.available returns the catalog's providers this process has
credentials for, tagged apiKey (host environment) or oauth (a stored
login), and filters by either. It reads the OAuth credential file from
ModelCatalog.Credentials, which Harness.layer provides once and model
calls now share, so listing and calling can't disagree. No network call
or token refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The model selector used its own credential checks and always read the
default OAuth file, so under `codework acp --home <dir>` it could hide a
provider the agent can call. It now uses ModelCatalog.available.

Adds an E2E test for the selector's providers and for the settings
model block setting a new session's model and thinking level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A settings `models` array holds aikit Model.Info entries, read exactly
like entries of the generated catalog: appended to it, and replacing a
catalog model with the same provider and id. Entries accumulate across
settings files, user then project, so the last declaration wins.

Key values are "${NAME}" references, read from the host environment when
the model is called: `provider.key` in an entry and the new
`model.options.apiKey`. Literals are rejected, so settings only ever hold
variable names. `apiKey` stays out of the provider options bag.

ModelCatalog.effective builds the catalog a session sees, LLM.resolve
prefers settings entries, and ModelCatalog.available takes the session's
hostDir and reports keyless providers as "none". Settings decode errors
now show messages written by schema checks; other failures still read
"invalid value" so a rejected value is never echoed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The model selector and set_config_option validation use the effective
catalog for the session's project, so models defined in settings can be
chosen from the editor.

Adds E2E tests: selector availability, user and project shadowing,
switching to a keyless model and rejected settings, plus a live call to a
settings-defined provider using a "${OPENROUTER_API_KEY}" key reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Revive the durable `session.config.changed` event and project it into a new
`session.config` JSON column, so a session's chosen model and thinking level
survive a restart. `Session.create` and `Session.attach` publish it; the
in-memory bindings keep only process-local options. Expose
`Session.configuration(id)`, resolved by the same composition a turn uses.

Refs COD-79

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop the ACP `selections` map; `session/new`, `session/load` and
`session/set_config_option` read `Session.configuration`, so a model picked
in the editor survives an agent restart.

Refs COD-79

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
User images and tool-result images sent to a model whose input does not
include "image" become text placeholders; consecutive images collapse to
one. Vision models are unchanged.

Fixes COD-38, COD-39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`PromptSchema.Prompt` becomes ordered `{ parts: [text | image] }`, mirroring
aikit's user message parts; the projector copies them into the user message
and the title comes from the text. Images are checked before anything is
saved (png/jpeg/gif/webp, at most 4.5 MB of base64, valid base64) and refused
with `InvalidPromptError`. `Session.PromptInput` accepts `string`, `{ text }`
or `{ parts }`; `Prompt.fromText` covers text-only callers.

Refs COD-86

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`image` blocks and embedded `image/*` resources become image parts, and
`initialize` advertises `promptCapabilities.image`. A refused image answers
`-32602` with the reason; `session/load` replays user images. The server
builds text prompts with `Prompt.fromText`.

Refs COD-86

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The live ACP cases (settings-defined model, stream/tool/cancel/fail/replay,
image prompt) run once per model: openai and openai-codex gpt-5.6-luna,
anthropic claude-sonnet-5-5, and deepseek-v4.1-flash, gemini-3.8-flash and
muse-spark-1.3-contributor via OpenRouter. Codex uses the local login's
access token without touching the login file. One artifact per model.

Refs COD-86

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`runner.llm.live` and `settings.live` run for the same six models as ACP,
listed in `test/utils/live.ts`. The thinking case asks for `high` (an adaptive
Claude model often skips thinking at `medium`), the interrupt case fails after
60 s instead of hanging, and OpenAI-only request fields are checked only for
OpenAI and Codex. The Anthropic interrupt case fails until COD-93.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… local login

OpenRouter suites run for deepseek-v4.1-flash, gemini-3.8-flash and
muse-spark-1.3-contributor instead of glm-5.3-flash. Codex suites take the
local login's access token when `OPENAI_CODEX_API_KEY` is unset. The abort
follow-up and the OpenRouter token-abort case get room for thinking models.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the vendored t3code effect-acp package with @agentclientprotocol/sdk
behind a small Effect adapter (src/acp/agent.ts). Handlers keep their
Effect-returning interface; requests run as fibers interrupted on client
cancellation or disconnect. The E2E suite drives the agent with the SDK's
client. Artifacts change only in key order: responses are now recorded in
wire order rather than re-encoded in schema order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the Codex login token lookup into test/env.ts alongside it, mirroring
aikit's test env setup. Only the ACP suite imports it, so the other CLI
tests keep a key-free environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sanchitrk
sanchitrk merged commit 1f8fbd0 into dev Sep 30, 2026
@sanchitrk
sanchitrk deleted the feature/acp-support branch October 8, 2026 06:18
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.

3 participants