Skip to content

Commit 2f2ae16

Browse files
committed
update claude adapter with upstream
1 parent 97d0d2a commit 2f2ae16

11 files changed

Lines changed: 673 additions & 139 deletions

File tree

packages/agent/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@
120120
"vitest": "^2.1.8"
121121
},
122122
"dependencies": {
123-
"@agentclientprotocol/sdk": "0.22.1",
124-
"@anthropic-ai/claude-agent-sdk": "0.3.156",
123+
"@agentclientprotocol/sdk": "0.25.0",
124+
"@anthropic-ai/claude-agent-sdk": "0.3.165",
125125
"@anthropic-ai/sdk": "0.100.1",
126126
"@hono/node-server": "^1.19.9",
127127
"@opentelemetry/api-logs": "^0.208.0",
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---
2+
name: upgrade-claude-adapter
3+
description: >-
4+
Sync this fork of @anthropic-ai/claude-agent-acp (packages/agent/src/adapters/claude)
5+
with a newer upstream release: bump the claude-agent-sdk / @agentclientprotocol/sdk,
6+
port upstream bug fixes and new SDK message handling, preserve the fork's divergences,
7+
verify, and update UPSTREAM.md. Use when asked to "upgrade/sync the claude adapter",
8+
"bump the agent SDK", or "port upstream claude-agent-acp changes".
9+
---
10+
11+
# Upgrade the Claude ACP adapter (upstream sync)
12+
13+
This is a runbook for syncing our **fork** of `@anthropic-ai/claude-agent-acp` (the upstream
14+
Zed/agentclientprotocol ACP agent) that lives in `packages/agent/src/adapters/claude/` with a newer
15+
upstream release. The fork is heavily diverged. The job is to port the *valuable* upstream changes
16+
(SDK bumps, bug fixes, new SDK-message handling) while preserving every intentional divergence — not
17+
to make the fork identical to upstream.
18+
19+
`UPSTREAM.md` (this directory) is the source of truth for the **fork point**, **last-synced
20+
version/commit**, the **file mapping**, the **PostHog-only code**, and the **intentional
21+
divergences**. Read it first, update it last.
22+
23+
> This file is a runbook, not an auto-registered slash command. Invoke it by telling Claude to
24+
> "follow the upgrade skill in the claude adapter dir." Move it to `.claude/skills/<name>/SKILL.md`
25+
> if you ever want it runnable as `/<name>`.
26+
27+
## Inputs you need before starting
28+
29+
1. **Upstream source checkout** — a local git clone of `github.com/agentclientprotocol/claude-agent-acp`.
30+
You need its history to diff. If the user hasn't given the path, **ask for it** (it's usually
31+
somewhere like `~/Cloud/claude-agent-acp`). Do not guess.
32+
2. **This repo** — the fork under `packages/agent/`.
33+
34+
## Process
35+
36+
### 0. Orient (read, don't write)
37+
38+
- Read `UPSTREAM.md`. Note **Last sync** (commit + version), the pinned **SDK** versions, the
39+
**File Mapping**, **PostHog Code-Only Code (Do Not Sync)**, and **Intentional Divergences**.
40+
- In the upstream checkout, list the change set since the last sync and skim the changelog:
41+
- `git -C <upstream> log --oneline <last-sync-sha>..HEAD`
42+
- `git -C <upstream> show <upstream>/CHANGELOG.md:CHANGELOG.md` (or just read `CHANGELOG.md`)
43+
- Confirm the new target version + HEAD sha and the target SDK versions from the upstream
44+
`package.json`.
45+
46+
### 1. Triage every commit
47+
48+
Bucket each commit since the last sync:
49+
50+
- **Port** — bug fixes and new feature / SDK-message handling that are *not* in the PostHog-only
51+
list and don't fight a divergence.
52+
- **Dep bump** — record the target SDK versions; the diff tells you if code changes ride along.
53+
- **Skip**`chore(main): release …`, `actions/* ` CI bumps, pure dependabot **dev**-dep bumps, and
54+
anything matching the PostHog-only / divergence lists.
55+
56+
Read intent from source diffs (exclude tests + JSON first):
57+
58+
```
59+
git -C <upstream> show <sha> -- src/ ':(exclude)src/tests/*' ':(exclude)*.json'
60+
```
61+
62+
A dependabot SDK-bump commit often *also* carries real code (new message handling). Don't assume
63+
"deps" == "no code".
64+
65+
### 2. Map upstream → fork
66+
67+
Upstream is one large `src/acp-agent.ts`; our fork is split. Use the File Mapping in `UPSTREAM.md`.
68+
Rough guide:
69+
70+
| Upstream | Fork |
71+
| --- | --- |
72+
| `acp-agent.ts` prompt loop, lifecycle, cancel | `claude-agent.ts` |
73+
| inline message/stream/result/system conversion | `conversion/sdk-to-acp.ts` |
74+
| inline prompt→SDK conversion | `conversion/acp-to-sdk.ts` |
75+
| `tools.ts` (tool_use→ACP, PostToolUse hook) | `conversion/tool-use-to-acp.ts`, `hooks.ts` |
76+
| model alias resolution | `session/models.ts`, `session/model-config.ts` |
77+
| options / system prompt | `session/options.ts` |
78+
| permissions | `permissions/*` |
79+
80+
For each upstream change, `rg` the fork for the touched symbol first — the fork usually already has a
81+
diverged version of it, so you're editing, not adding.
82+
83+
### 3. Bump dependencies
84+
85+
In `packages/agent/package.json`, set `@anthropic-ai/claude-agent-sdk`, `@agentclientprotocol/sdk`,
86+
and `@anthropic-ai/sdk` to the upstream `package.json` versions, then `pnpm install` from the repo
87+
root. (`packages/shared` pins its own older `@agentclientprotocol/sdk`; leave it unless a
88+
cross-package type error forces a bump.)
89+
90+
### 4. Find the breaking-change surface
91+
92+
Run `pnpm --filter agent typecheck`. The errors are your ACP/SDK breaking-change list. Gotchas seen
93+
in past syncs:
94+
95+
- **The ACP SDK ships name-mangled generated types.** `dist/schema/*.gen.d.ts` shows enum literals as
96+
`n` (e.g. `StopReason = "…" | "n" | "cancelled"`). Don't trust grep there. Read the hand-written
97+
`dist/acp.d.ts`, or download the exact target to inspect cleanly:
98+
```
99+
cd /tmp && npm pack @agentclientprotocol/sdk@<ver> && tar xzf *.tgz
100+
rg -n "type StopReason|deleteSession|SessionModelState" package/dist/schema/types.gen.d.ts package/dist/acp.d.ts
101+
```
102+
- **`node -e "require('<pkg>/package.json')"` may fail** on the SDKs (exports map blocks the subpath).
103+
Read `node_modules/<pkg>/package.json` directly for the installed version.
104+
- **An ACP SDK bump can break code outside the claude adapter.** The whole `packages/agent` package
105+
must typecheck — expect to also fix `adapters/codex/*` and `server/agent-server.ts`. Keep those
106+
fixes minimal and behavior-preserving (e.g. when ACP removed the `models` response field, the codex
107+
adapter derived the model id from `configOptions` instead).
108+
109+
### 5. Port in phases — bug fixes first, then features
110+
111+
For each ported change:
112+
113+
- **Preserve divergences** (see `UPSTREAM.md` → Intentional Divergences + PostHog-only). The big ones:
114+
single-session `this.session` (not `this.sessions[id]`); `interruptReason` on cancel; gateway models
115+
via `fetchGatewayModels` (not `initializationResult.models`); `_posthog/*` ext notifications;
116+
the "Unsupported slash command" gate on `knownSlashCommands`; `SYSTEM_REMINDER` stripping; plan /
117+
questions / MCP-metadata machinery.
118+
- **New SDK `system` subtypes are safe by default.** `handleSystemMessage` ends in `default: break`,
119+
and the prompt-loop top-level `switch (message.type)` only `unreachable()`s unknown top-level
120+
*types*. So a new subtype won't crash the loop — port real handling only where there's user value
121+
(e.g. `permission_denied` → failed tool_call, `tool_progress` → in_progress, `commands_changed`
122+
available_commands_update, `mirror_error` → log).
123+
- When upstream reads new fields (`stop_details`, `getContextUsage`, `thinking`), confirm the
124+
installed SDK `.d.ts` actually has them before porting. Skip ports the fork can't use (e.g. the
125+
fork doesn't read `MAX_THINKING_TOKENS`, so upstream's `resolveThinkingConfig` was N/A).
126+
- Typecheck after each logical group, not just at the end.
127+
128+
### 6. Verify (all of it)
129+
130+
```
131+
pnpm --filter agent typecheck
132+
pnpm --filter agent build
133+
npx biome check --write <changed files> # biome is the formatter/linter, not prettier/eslint
134+
pnpm typecheck # whole repo: confirms apps/code compiles vs the new ACP SDK
135+
pnpm --filter agent test
136+
pnpm --filter code test
137+
```
138+
139+
- The `apps/code` renderer unit tests `analytics.test.ts` and `panelLayoutStore.test.ts` are **flaky**
140+
— they sometimes throw in `getElectronTRPC` / electron-trpc `ipcLink` depending on test ordering. If
141+
they fail, re-run; a clean rerun (or `git stash` + run on the clean tree) passing confirms it's the
142+
known flake, not your change.
143+
144+
### 7. Update `UPSTREAM.md` (do this last)
145+
146+
- Bump **Last sync** (version + HEAD sha + date) and the pinned **SDK** versions.
147+
- Add `## Changes Ported in v<X> Sync` (one bullet per change, with PR # and short sha) and
148+
`## Skipped in v<X> Sync` (with the reason for each skip).
149+
- If a port made a former divergence match upstream, move it out of the Intentional Divergences table.
150+
151+
## Fork facts worth remembering
152+
153+
- **Single session.** The agent owns one `this.session` (from `BaseAcpAgent`), not a `sessions` map.
154+
Upstream's per-session refactors usually collapse to "just use `this.session`".
155+
- **Renderer uses config options only.** Model/mode/effort selection is `SessionConfigOption` end to
156+
end; the renderer never reads the legacy `models` response field or calls `unstable_setSessionModel`.
157+
That's why upstream's ACP-0.24/0.25 model-state removals are safe to follow.
158+
- **`toolUseCache` is never cleared** in the fork (created once in the constructor), so long sessions
159+
accumulate — keep the prune-at-tool_result behavior, and make any PostToolUse hook close over the
160+
data it needs rather than re-reading the cache.
161+
- **Conversion is split out.** `claude-agent.ts` calls `handleSystemMessage` / `handleStreamEvent` /
162+
`handleResultMessage` / `handleUserAssistantMessage` from `conversion/sdk-to-acp.ts`. Upstream
163+
inlines all of this in `acp-agent.ts`.
164+
- **Don't commit or push** unless the user explicitly asks. Leave the work on the current branch.

packages/agent/src/adapters/claude/UPSTREAM.md

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
55
## Fork Point
66

77
- **Forked**: v0.10.9, commit `5411e0f4`, Dec 2 2025
8-
- **Last sync**: v0.39.0, commit `51a370e`, May 29 2026
9-
- **SDK**: `@anthropic-ai/claude-agent-sdk` 0.3.156, `@agentclientprotocol/sdk` 0.22.1, `@anthropic-ai/sdk` 0.100.1
8+
- **Last sync**: v0.42.0, commit `0dbccf5`, Jun 5 2026
9+
- **SDK**: `@anthropic-ai/claude-agent-sdk` 0.3.165, `@agentclientprotocol/sdk` 0.25.0, `@anthropic-ai/sdk` 0.100.1
1010

1111
## File Mapping
1212

@@ -55,6 +55,70 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
5555
| Shutdown on ACP close | Process exits | No standalone process | Agent is embedded in server |
5656
| Unsupported slash commands | Loops silently on early idle | Emits "Unsupported slash command" chunk, gated on `initializationResult().commands` so plugin/skill commands (e.g. `/skills-store`) whose echoes use a fresh uuid are not false-flagged | The SDK consumes some slash commands without producing output (e.g. `/plugin` in non-interactive mode); without this we hang. The known-commands gate avoids racing plugin/skill loads where idle can arrive before the transformed user-message echo. |
5757

58+
## Changes Ported in v0.42.0 Sync
59+
60+
- **SDK bumps**: claude-agent-sdk 0.3.156 -> 0.3.165, ACP SDK 0.22.1 -> 0.25.0, anthropic SDK
61+
unchanged at 0.100.1.
62+
- **ACP SDK 0.25.0 model-state removal** (#737, 32175b8): 0.24.0 deleted `SessionModelState`,
63+
`SetSessionModelRequest/Response`, `ModelInfo`, and the `models` field on every session lifecycle
64+
response; model selection moved entirely into `SessionConfigOption` (category "model"). Our fork
65+
already drove model selection through config options, so this just removed the vestigial legacy
66+
path: dropped those imports, the `unstable_setSessionModel` method, and the `models` build/return
67+
in `createSession` / `getExistingSessionState` / `loadSession`. The codex adapter's
68+
`response.models?.currentModelId` read was replaced with a `modelIdFromConfigOptions()` helper
69+
(codex `models.ts`). Verified the renderer reads only `configOptions`, never `.models`.
70+
- **ACP SDK 0.25.0 `deleteSession` rename** (#753, 0dbccf5): No-op for us — our fork never
71+
implemented `unstable_deleteSession`, and the method is optional on the `Agent` interface.
72+
- **Refusal handling** (SDK 0.3.162, #740, add7e31): Capture the refused assistant message's
73+
`stop_details.explanation`; the terminal `result` (stop_reason "refusal") emits it as an
74+
`agent_message_chunk` and returns ACP's dedicated `refusal` stop reason instead of letting the
75+
`is_error` path surface it as an internal error.
76+
- **commands_changed** (SDK 0.3.162, #740, add7e31): New `system` subtype handled inline in the
77+
prompt loop — pushes `available_commands_update` straight from `message.commands` (rather than
78+
re-querying `supportedCommands()`, which only ever reflects the init list) and refreshes
79+
`session.knownSlashCommands` so the unsupported-slash-command gate stays accurate.
80+
- **Optimized marker stripping** (#738, 895422c): `stripMarkerTags` rewritten as a single-pass
81+
scanner in `conversion/sdk-to-acp.ts`, removing the `[\s\S]*?` backtracking risk on pathological
82+
input.
83+
- **Force-cancel backstop** (#742, cffea4b): Added per-turn `cancelController` + `forceCancelTimer`
84+
on `Session` and a mutable `forceCancelGraceMs` (30s) on the agent. The prompt loop races
85+
`query.next()` against the cancel signal; `interrupt()` arms a grace-period timer that aborts it,
86+
so a wedged SDK that never yields after interrupt (issue #680, e.g. a blocking `TaskOutput` poll)
87+
returns "cancelled" instead of hanging. Adapted to our single-session model; preserves the
88+
`interruptReason` meta on the forced return.
89+
- **Cross-family model match fix** (#731, f4704c1): `scoreModelMatch` (session/models.ts) now
90+
returns 0 when only the context-hint token matched, so `claude-opus-4-6[1m]` can't resolve to
91+
`sonnet[1m]` purely on the shared "1m" token. Layers on top of our existing
92+
`modelVersionsCompatible` filter.
93+
- **compact_boundary getContextUsage** (#747, 398f763): compact_boundary now fetches the
94+
authoritative post-compaction `used` via `query.getContextUsage()` (helper
95+
`fetchContextUsedTokens`), falling back to 0 on failure. `size` still comes from the
96+
gateway-learned window (getContextUsage under-reports 1M windows). Our fork-specific
97+
`promptReplayed = true` side effect is preserved.
98+
- **New SDK message handling** (#747, 398f763): `tool_progress` -> `tool_call_update` `in_progress`
99+
with `elapsedTimeSeconds`; `rate_limit_event` -> `usage_update` carrying `_claude/rateLimit`;
100+
`permission_denied` -> `tool_call_update` `failed` (in `handleSystemMessage`); `mirror_error` ->
101+
logged (history-persistence failure / potential data loss on resume).
102+
- **Prune tool cache** (#748, ec14211): `toolUseCache` was never cleared in our fork (set once in
103+
the constructor, accumulated for the whole agent lifetime). Now pruned at `tool_result` time. The
104+
PostToolUse hook closes over the tool name + bash command instead of re-reading the cache, so the
105+
Edit/Write diff survives any hook/result reordering. We did NOT adopt upstream's per-session cache
106+
move (we are single-session) or its `backgroundTerminals` deletion.
107+
- **Test mock**: added `reloadSkills` to the SDK `MockQuery` (new method on the SDK `Query`
108+
interface in 0.3.165).
109+
110+
## Skipped in v0.42.0 Sync
111+
112+
- **Message ids** (#750, 18516a3): Upstream records an ACP `messageId` -> SDK uuid map for a future
113+
fork/rewind feature, explicitly "NOT READ YET". We don't consume it, it adds a `Session` field and
114+
threads `messageId` through many `toAcpNotifications` call sites, so it is deferred until we wire
115+
up rewind. (ACP 0.25.0 does expose the `messageId` field, so the port is unblocked when wanted.)
116+
- **resolveThinkingConfig** (#747, 398f763): Upstream maps the legacy `MAX_THINKING_TOKENS` env var
117+
to the SDK's new `thinking` option. Our fork never reads `MAX_THINKING_TOKENS` (model setup is
118+
gateway-driven via `session/options.ts`), so there is nothing to migrate.
119+
- **Pure dep-group / release / CI bumps** (#736, #741, #745, #728, #743): No fork-relevant code
120+
beyond the SDK versions captured above.
121+
58122
## Changes Ported in v0.30.0 Sync
59123

60124
- **SDK bumps**: claude-agent-sdk 0.2.112 -> 0.2.114, ACP SDK 0.16.1 -> 0.19.0, anthropic SDK -> 0.89.0
@@ -165,7 +229,7 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
165229

166230
## Next Sync
167231

168-
1. Check upstream changelog since v0.37.0
232+
1. Check upstream changelog since v0.42.0
169233
2. Diff upstream source against PostHog Code using the file mapping above
170234
3. Port in phases: bug fixes first, then features
171235
4. After each phase: `pnpm --filter agent typecheck && pnpm --filter agent build && pnpm lint`

0 commit comments

Comments
 (0)