@agentclientprotocol/claude-agent-acp 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { Agent, AgentSideConnection, AuthenticateRequest, CancelNotification, ClientCapabilities, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, ListSessionsRequest, ListSessionsResponse, LoadSessionRequest, LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, ResumeSessionRequest, ResumeSessionResponse, SessionConfigOption, SessionModeState, SessionNotification, SetSessionConfigOptionRequest, SetSessionConfigOptionResponse, SetSessionModeRequest, SetSessionModeResponse, CloseSessionRequest, CloseSessionResponse, DeleteSessionRequest, DeleteSessionResponse, TerminalHandle, TerminalOutputResponse, WriteTextFileRequest, WriteTextFileResponse } from "@agentclientprotocol/sdk";
1
+ import { Agent, AgentSideConnection, AuthenticateRequest, CancelNotification, ClientCapabilities, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, ListSessionsRequest, ListSessionsResponse, LoadSessionRequest, LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, ResumeSessionRequest, ResumeSessionResponse, SessionConfigOption, SessionModeState, SessionNotification, SetSessionConfigOptionRequest, SetSessionConfigOptionResponse, SetSessionModeRequest, SetSessionModeResponse, CloseSessionRequest, CloseSessionResponse, DeleteSessionRequest, DeleteSessionResponse, WriteTextFileRequest, WriteTextFileResponse } from "@agentclientprotocol/sdk";
2
2
  import { CanUseTool, ModelInfo, Options, PermissionMode, PermissionUpdate, Query, SDKMessageOrigin, SDKPartialAssistantMessage, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
3
3
  import { ContentBlockParam } from "@anthropic-ai/sdk/resources";
4
4
  import { BetaContentBlock, BetaRawContentBlockDelta } from "@anthropic-ai/sdk/resources/beta.mjs";
@@ -73,14 +73,30 @@ type Session = {
73
73
  /** Accumulated task list for the session, keyed by task ID. Task IDs are
74
74
  * per-session, so this state must not be shared across sessions. */
75
75
  taskState: TaskState;
76
- };
77
- type BackgroundTerminal = {
78
- handle: TerminalHandle;
79
- status: "started";
80
- lastOutput: TerminalOutputResponse | null;
81
- } | {
82
- status: "aborted" | "exited" | "killed" | "timedOut";
83
- pendingOutput: TerminalOutputResponse;
76
+ /** Caches `tool_use` blocks by id so the matching `tool_result` can recover
77
+ * the tool name/input when mapping it to a `tool_call_update`. Per-session
78
+ * (tool_use ids are only unique within a session) and pruned at
79
+ * `tool_result` time so a long-running session doesn't accumulate every
80
+ * tool call for its whole lifetime. */
81
+ toolUseCache: ToolUseCache;
82
+ /** Maps the ACP `messageId` we expose to clients (see `messageIdForGrouping`)
83
+ * to the SDK message uuid that the Agent SDK's rewind/resume APIs key on
84
+ * (`Query.rewindFiles` takes a user-message uuid; `resumeSessionAt` takes an
85
+ * `SDKAssistantMessage.uuid`). For assistant turns the two differ — the ACP
86
+ * id is the Anthropic API message id (`msg_…`), available at `message_start`
87
+ * so streamed chunks can carry it, while the uuid only arrives on the
88
+ * consolidated message — so a client can only ask to rewind/fork by the id it
89
+ * was given, and we need this table to translate it back.
90
+ *
91
+ * Populated as a byproduct of the message loop (the consolidated message
92
+ * carries both ids) and of `replaySessionHistory` on load, so no extra
93
+ * `getSessionMessages` read is needed at rewind time. Last-write-wins
94
+ * naturally yields the turn-boundary uuid when one `msg_…` spans several
95
+ * content-block messages.
96
+ *
97
+ * NOT READ YET — recorded now so the mapping exists if/when we wire up
98
+ * fork/rewind. */
99
+ messageIdToUuid: Map<string, string>;
84
100
  };
85
101
  export type SDKMessageFilter = {
86
102
  type: string;
@@ -188,10 +204,6 @@ export declare class ClaudeAcpAgent implements Agent {
188
204
  [key: string]: Session;
189
205
  };
190
206
  client: AgentSideConnection;
191
- toolUseCache: ToolUseCache;
192
- backgroundTerminals: {
193
- [key: string]: BackgroundTerminal;
194
- };
195
207
  clientCapabilities?: ClientCapabilities;
196
208
  logger: Logger;
197
209
  gatewayAuthRequest?: GatewayAuthRequest;
@@ -230,6 +242,22 @@ export declare class ClaudeAcpAgent implements Agent {
230
242
  private createSession;
231
243
  }
232
244
  export declare function promptToClaude(prompt: PromptRequest): SDKUserMessage;
245
+ /**
246
+ * Resolves the ACP `messageId` for a Claude SDK message (live) or a persisted
247
+ * transcript message (replay) so chunk grouping is identical in both views.
248
+ *
249
+ * Assistant turns are keyed by the Anthropic API message id (`message.id`),
250
+ * which is identical at `message_start`, on the consolidated assistant message,
251
+ * and in the persisted transcript — unlike the per-`stream_event` uuid, which is
252
+ * unique per event and never persisted. User messages have no API id, but they
253
+ * are never streamed, so their (stable) SDK uuid is used instead. ACP message
254
+ * ids are opaque strings, so no particular format is required.
255
+ */
256
+ export declare function messageIdForGrouping(message: {
257
+ type?: string;
258
+ uuid?: string | null;
259
+ message?: unknown;
260
+ }): string | undefined;
233
261
  /**
234
262
  * Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
235
263
  * Only handles text, image, and thinking chunks for now.
@@ -240,11 +268,13 @@ export declare function toAcpNotifications(content: string | ContentBlockParam[]
240
268
  parentToolUseId?: string | null;
241
269
  cwd?: string;
242
270
  taskState?: TaskState;
271
+ messageId?: string;
243
272
  }): SessionNotification[];
244
273
  export declare function streamEventToAcpNotifications(message: SDKPartialAssistantMessage, sessionId: string, toolUseCache: ToolUseCache, client: AgentSideConnection, logger: Logger, options?: {
245
274
  clientCapabilities?: ClientCapabilities;
246
275
  cwd?: string;
247
276
  taskState?: TaskState;
277
+ messageId?: string;
248
278
  }): SessionNotification[];
249
279
  export declare function runAcp(): {
250
280
  connection: AgentSideConnection;
@@ -1 +1 @@
1
- {"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"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"}
package/dist/acp-agent.js CHANGED
@@ -243,14 +243,12 @@ export function describeAlwaysAllow(suggestions, toolName) {
243
243
  // Implement the ACP Agent interface
244
244
  export class ClaudeAcpAgent {
245
245
  constructor(client, logger) {
246
- this.backgroundTerminals = {};
247
246
  /** Grace period before a `session/cancel` forces a wedged prompt loop to
248
247
  * return "cancelled". See {@link DEFAULT_FORCE_CANCEL_GRACE_MS}. Mutable so
249
248
  * tests can shrink it. */
250
249
  this.forceCancelGraceMs = DEFAULT_FORCE_CANCEL_GRACE_MS;
251
250
  this.sessions = {};
252
251
  this.client = client;
253
- this.toolUseCache = {};
254
252
  this.logger = logger ?? console;
255
253
  }
256
254
  async initialize(request) {
@@ -490,6 +488,17 @@ export class ClaudeAcpAgent {
490
488
  // `compacting` status sets it again, so every distinct compaction (e.g.
491
489
  // repeated auto-compactions in a long turn) is still shown.
492
490
  let compactionInProgress = false;
491
+ // Holds the Anthropic API message id of the assistant message currently
492
+ // being streamed, captured from `message_start` so every streamed chunk can
493
+ // be tagged with it. We use the API message id rather than the
494
+ // per-`stream_event` uuid because the same id is also present on the
495
+ // consolidated assistant message and in the persisted transcript — so a turn
496
+ // keeps the same ACP `messageId` whether it is streamed live or replayed
497
+ // from history. The per-event uuid is unique per event and never persisted.
498
+ // A single value suffices because every streaming partial arrives with
499
+ // `parent_tool_use_id === null` (subagent work is folded into tool-result
500
+ // messages, never surfaced as partial streams).
501
+ let currentStreamMessageId;
493
502
  const userMessage = promptToClaude(params);
494
503
  const promptUuid = randomUUID();
495
504
  userMessage.uuid = promptUuid;
@@ -591,28 +600,35 @@ export class ClaudeAcpAgent {
591
600
  break;
592
601
  }
593
602
  case "compact_boundary": {
594
- // Send used:0 immediately so the client doesn't keep showing
595
- // the stale pre-compaction context size until the next turn.
603
+ // Refresh the displayed usage immediately so the client doesn't
604
+ // keep showing the stale pre-compaction size (e.g. "944k/1m")
605
+ // right after the user sees "Compacting completed", which is
606
+ // confusing and wrong.
596
607
  //
597
- // This is a deliberate approximation: we don't know the exact
598
- // post-compaction token count (only the SDK's next API call
599
- // reveals that). But used:0 is directionally correct — context
600
- // just dropped dramatically — and the real value replaces it
601
- // within seconds when the next result message arrives.
602
- // The alternative (no update) leaves the client showing e.g.
603
- // "944k/1m" right after the user sees "Compacting completed",
604
- // which is confusing and wrong.
608
+ // Prefer the SDK's authoritative post-compaction `used` via
609
+ // getContextUsage — it reflects the real retained context
610
+ // (system prompt + tools + surviving messages), which the
611
+ // per-message API usage numbers can't give us until the next
612
+ // turn's result. If the control request fails, fall back to the
613
+ // used:0 approximation: directionally correct (context just
614
+ // dropped dramatically) and replaced within seconds by the next
615
+ // result message.
616
+ //
617
+ // `size` keeps coming from session.contextWindowSize (learned
618
+ // from modelUsage / the model heuristic) — getContextUsage's
619
+ // window field under-reports extended 1M windows.
605
620
  //
606
621
  // The "Compacting completed." text is emitted from the `status`
607
622
  // handler (keyed on `compact_result`), not here, so the failure
608
623
  // path gets a message too.
609
- lastAssistantTotalUsage = 0;
624
+ const usedTokens = await fetchContextUsedTokens(session.query, this.logger);
610
625
  lastAssistantUsage = null;
626
+ lastAssistantTotalUsage = usedTokens ?? 0;
611
627
  await this.client.sessionUpdate({
612
628
  sessionId: message.session_id,
613
629
  update: {
614
630
  sessionUpdate: "usage_update",
615
- used: 0,
631
+ used: lastAssistantTotalUsage,
616
632
  size: session.contextWindowSize,
617
633
  },
618
634
  });
@@ -690,6 +706,47 @@ export class ClaudeAcpAgent {
690
706
  });
691
707
  break;
692
708
  }
709
+ case "mirror_error": {
710
+ // The SDK failed to persist session history (SessionStore
711
+ // append rejected/timed out after retry) — potential data loss
712
+ // the user should know about rather than a silent gap on
713
+ // resume. Log it and surface a warning in the conversation.
714
+ this.logger.error(`Session ${message.session_id}: failed to persist history: ${message.error}`);
715
+ break;
716
+ }
717
+ case "permission_denied": {
718
+ // A tool call was auto-denied (by a rule, the classifier,
719
+ // dontAsk mode, etc.) before running. The tool_use block was
720
+ // already emitted as a `tool_call`, so mark it failed with the
721
+ // rejection reason — otherwise the client shows a tool call
722
+ // that silently never resolves.
723
+ const reason = message.decision_reason ?? message.message;
724
+ await this.client.sessionUpdate({
725
+ sessionId: message.session_id,
726
+ update: {
727
+ sessionUpdate: "tool_call_update",
728
+ toolCallId: message.tool_use_id,
729
+ status: "failed",
730
+ content: [
731
+ {
732
+ type: "content",
733
+ content: { type: "text", text: `Permission denied: ${reason}` },
734
+ },
735
+ ],
736
+ _meta: {
737
+ claudeCode: {
738
+ toolName: message.tool_name,
739
+ toolResponse: {
740
+ decisionReasonType: message.decision_reason_type,
741
+ decisionReason: message.decision_reason,
742
+ message: message.message,
743
+ },
744
+ },
745
+ },
746
+ },
747
+ });
748
+ break;
749
+ }
693
750
  case "hook_started":
694
751
  case "hook_progress":
695
752
  case "hook_response":
@@ -702,8 +759,6 @@ export class ClaudeAcpAgent {
702
759
  case "plugin_install":
703
760
  case "notification":
704
761
  case "api_retry":
705
- case "mirror_error":
706
- case "permission_denied":
707
762
  case "thinking_tokens":
708
763
  // Todo: process via status api: https://docs.claude.com/en/docs/claude-code/hooks#hook-output
709
764
  break;
@@ -795,7 +850,7 @@ export class ClaudeAcpAgent {
795
850
  // Task-notification followups never originate from a user
796
851
  // slash command, so skip the forwarding for them.
797
852
  if (isLocalOnlyCommand && !isTaskNotification) {
798
- for (const notification of toAcpNotifications(message.result, "assistant", params.sessionId, this.toolUseCache, this.client, this.logger)) {
853
+ for (const notification of toAcpNotifications(message.result, "assistant", params.sessionId, session.toolUseCache, this.client, this.logger)) {
799
854
  await this.client.sessionUpdate(notification);
800
855
  }
801
856
  }
@@ -833,6 +888,12 @@ export class ClaudeAcpAgent {
833
888
  break;
834
889
  }
835
890
  case "stream_event": {
891
+ // `message_start` carries the Anthropic API message id; capture it
892
+ // so the streamed chunks that follow (whose delta events don't carry
893
+ // it) can all be tagged with the same, replay-stable id.
894
+ if (message.event.type === "message_start") {
895
+ currentStreamMessageId = message.event.message.id || undefined;
896
+ }
836
897
  if (message.parent_tool_use_id === null &&
837
898
  (message.event.type === "message_start" || message.event.type === "message_delta")) {
838
899
  if (message.event.type === "message_start") {
@@ -880,10 +941,11 @@ export class ClaudeAcpAgent {
880
941
  });
881
942
  }
882
943
  }
883
- for (const notification of streamEventToAcpNotifications(message, params.sessionId, this.toolUseCache, this.client, this.logger, {
944
+ for (const notification of streamEventToAcpNotifications(message, params.sessionId, session.toolUseCache, this.client, this.logger, {
884
945
  clientCapabilities: this.clientCapabilities,
885
946
  cwd: session.cwd,
886
947
  taskState: session.taskState,
948
+ messageId: currentStreamMessageId,
887
949
  })) {
888
950
  await this.client.sessionUpdate(notification);
889
951
  }
@@ -894,6 +956,14 @@ export class ClaudeAcpAgent {
894
956
  if (session.cancelled) {
895
957
  break;
896
958
  }
959
+ // Record the ACP messageId -> SDK uuid mapping for this message. The
960
+ // consolidated message carries both ids, so this is where we learn
961
+ // the uuid that the SDK's rewind/resume APIs key on for the id we
962
+ // hand clients. Not read yet (see Session.messageIdToUuid).
963
+ const mappedMessageId = messageIdForGrouping(message);
964
+ if (mappedMessageId && typeof message.uuid === "string" && message.uuid.length > 0) {
965
+ session.messageIdToUuid.set(mappedMessageId, message.uuid);
966
+ }
897
967
  // Check for prompt replay
898
968
  if (message.type === "user" && "uuid" in message && message.uuid) {
899
969
  if (message.uuid === promptUuid) {
@@ -940,11 +1010,12 @@ export class ClaudeAcpAgent {
940
1010
  message.message.content.includes("<local-command-stdout>")) {
941
1011
  const stripped = stripLocalCommandMetadata(message.message.content);
942
1012
  if (typeof stripped === "string") {
943
- for (const notification of toAcpNotifications(stripped, message.message.role, params.sessionId, this.toolUseCache, this.client, this.logger, {
1013
+ for (const notification of toAcpNotifications(stripped, message.message.role, params.sessionId, session.toolUseCache, this.client, this.logger, {
944
1014
  clientCapabilities: this.clientCapabilities,
945
1015
  parentToolUseId: message.parent_tool_use_id,
946
1016
  cwd: session.cwd,
947
1017
  taskState: session.taskState,
1018
+ messageId: messageIdForGrouping(message),
948
1019
  })) {
949
1020
  await this.client.sessionUpdate(notification);
950
1021
  }
@@ -982,21 +1053,51 @@ export class ClaudeAcpAgent {
982
1053
  ? // Handled by stream events above
983
1054
  message.message.content.filter((item) => !["text", "thinking"].includes(item.type))
984
1055
  : message.message.content;
985
- for (const notification of toAcpNotifications(content, message.message.role, params.sessionId, this.toolUseCache, this.client, this.logger, {
1056
+ for (const notification of toAcpNotifications(content, message.message.role, params.sessionId, session.toolUseCache, this.client, this.logger, {
986
1057
  clientCapabilities: this.clientCapabilities,
987
1058
  parentToolUseId: message.parent_tool_use_id,
988
1059
  cwd: session.cwd,
989
1060
  taskState: session.taskState,
1061
+ messageId: messageIdForGrouping(message),
990
1062
  })) {
991
1063
  await this.client.sessionUpdate(notification);
992
1064
  }
993
1065
  break;
994
1066
  }
995
- case "tool_progress":
1067
+ case "tool_progress": {
1068
+ await this.client.sessionUpdate({
1069
+ sessionId: message.session_id,
1070
+ update: {
1071
+ sessionUpdate: "tool_call_update",
1072
+ toolCallId: message.tool_use_id,
1073
+ status: "in_progress",
1074
+ _meta: {
1075
+ claudeCode: {
1076
+ toolName: message.tool_name,
1077
+ toolResponse: { elapsedTimeSeconds: message.elapsed_time_seconds },
1078
+ },
1079
+ },
1080
+ },
1081
+ });
1082
+ break;
1083
+ }
1084
+ case "rate_limit_event": {
1085
+ if (lastAssistantTotalUsage !== null) {
1086
+ await this.client.sessionUpdate({
1087
+ sessionId: message.session_id,
1088
+ update: {
1089
+ sessionUpdate: "usage_update",
1090
+ used: lastAssistantTotalUsage,
1091
+ size: session.contextWindowSize,
1092
+ _meta: { "_claude/rateLimit": message.rate_limit_info },
1093
+ },
1094
+ });
1095
+ }
1096
+ break;
1097
+ }
996
1098
  case "tool_use_summary":
997
1099
  case "auth_status":
998
1100
  case "prompt_suggestion":
999
- case "rate_limit_event":
1000
1101
  break;
1001
1102
  default:
1002
1103
  unreachable(message);
@@ -1263,6 +1364,15 @@ export class ClaudeAcpAgent {
1263
1364
  const toolUseCache = {};
1264
1365
  const messages = await getSessionMessages(sessionId);
1265
1366
  for (const message of messages) {
1367
+ // Backfill the ACP messageId -> SDK uuid mapping for messages we didn't
1368
+ // observe live (resumed/loaded sessions), so rewind/resume can translate
1369
+ // a client-supplied id without an extra getSessionMessages read. Not read
1370
+ // yet (see Session.messageIdToUuid).
1371
+ const replayMessageId = messageIdForGrouping(message);
1372
+ const replaySession = this.sessions[sessionId];
1373
+ if (replaySession && replayMessageId && message.uuid) {
1374
+ replaySession.messageIdToUuid.set(replayMessageId, message.uuid);
1375
+ }
1266
1376
  // @ts-expect-error - untyped in SDK but we handle all of these
1267
1377
  let content = message.message.content;
1268
1378
  // @ts-expect-error - untyped in SDK but we handle all of these
@@ -1280,6 +1390,7 @@ export class ClaudeAcpAgent {
1280
1390
  clientCapabilities: this.clientCapabilities,
1281
1391
  cwd: this.sessions[sessionId]?.cwd,
1282
1392
  taskState: this.sessions[sessionId]?.taskState,
1393
+ messageId: replayMessageId,
1283
1394
  })) {
1284
1395
  await this.client.sessionUpdate(notification);
1285
1396
  }
@@ -1631,10 +1742,8 @@ export class ClaudeAcpAgent {
1631
1742
  // Extract options from _meta if provided
1632
1743
  const sessionMeta = params._meta;
1633
1744
  const userProvidedOptions = sessionMeta?.claudeCode?.options;
1634
- // Configure thinking tokens from environment variable
1635
- const maxThinkingTokens = process.env.MAX_THINKING_TOKENS
1636
- ? parseInt(process.env.MAX_THINKING_TOKENS, 10)
1637
- : undefined;
1745
+ // Configure thinking behavior from environment variable
1746
+ const thinking = resolveThinkingConfig(process.env.MAX_THINKING_TOKENS, this.logger);
1638
1747
  // Parse model configuration from environment (e.g. Bedrock model overrides)
1639
1748
  const modelConfig = parseModelConfig(process.env.CLAUDE_MODEL_CONFIG);
1640
1749
  // Disable this for now, not a great way to expose this over ACP at the moment (in progress work so we can revisit)
@@ -1653,7 +1762,7 @@ export class ClaudeAcpAgent {
1653
1762
  const options = {
1654
1763
  systemPrompt,
1655
1764
  settingSources: ["user", "project", "local"],
1656
- ...(maxThinkingTokens !== undefined && { maxThinkingTokens }),
1765
+ ...(thinking !== undefined && { thinking }),
1657
1766
  ...userProvidedOptions,
1658
1767
  // CLAUDE_MODEL_CONFIG env var is a fallback for model
1659
1768
  // configuration (e.g. Bedrock model ID overrides). When the caller
@@ -1870,6 +1979,8 @@ export class ClaudeAcpAgent {
1870
1979
  emitRawSDKMessages: sessionMeta?.claudeCode?.emitRawSDKMessages ?? false,
1871
1980
  contextWindowSize: inferContextWindowFromModel(models.currentModelId) ?? DEFAULT_CONTEXT_WINDOW,
1872
1981
  taskState,
1982
+ toolUseCache: {},
1983
+ messageIdToUuid: new Map(),
1873
1984
  };
1874
1985
  return {
1875
1986
  sessionId,
@@ -2111,11 +2222,16 @@ function tokenizeModelPreference(model) {
2111
2222
  function scoreModelMatch(model, tokens, contextHint) {
2112
2223
  const haystack = `${model.value} ${model.displayName}`.toLowerCase();
2113
2224
  let score = 0;
2225
+ let nonHintMatched = false;
2114
2226
  for (const token of tokens) {
2115
2227
  if (haystack.includes(token)) {
2228
+ if (token !== contextHint)
2229
+ nonHintMatched = true;
2116
2230
  score += token === contextHint ? 3 : 1;
2117
2231
  }
2118
2232
  }
2233
+ if (contextHint && !nonHintMatched)
2234
+ return 0;
2119
2235
  return score;
2120
2236
  }
2121
2237
  function resolveModelPreference(models, preference) {
@@ -2383,6 +2499,42 @@ export function promptToClaude(prompt) {
2383
2499
  parent_tool_use_id: null,
2384
2500
  };
2385
2501
  }
2502
+ /**
2503
+ * Resolves the ACP `messageId` for a Claude SDK message (live) or a persisted
2504
+ * transcript message (replay) so chunk grouping is identical in both views.
2505
+ *
2506
+ * Assistant turns are keyed by the Anthropic API message id (`message.id`),
2507
+ * which is identical at `message_start`, on the consolidated assistant message,
2508
+ * and in the persisted transcript — unlike the per-`stream_event` uuid, which is
2509
+ * unique per event and never persisted. User messages have no API id, but they
2510
+ * are never streamed, so their (stable) SDK uuid is used instead. ACP message
2511
+ * ids are opaque strings, so no particular format is required.
2512
+ */
2513
+ export function messageIdForGrouping(message) {
2514
+ if (message.type === "assistant") {
2515
+ const inner = message.message;
2516
+ const apiId = inner && typeof inner === "object" && "id" in inner
2517
+ ? inner.id
2518
+ : undefined;
2519
+ if (typeof apiId === "string" && apiId.length > 0) {
2520
+ return apiId;
2521
+ }
2522
+ }
2523
+ return typeof message.uuid === "string" && message.uuid.length > 0 ? message.uuid : undefined;
2524
+ }
2525
+ /**
2526
+ * Stamps an ACP `messageId` onto a session update, but only on the message/
2527
+ * thought chunk variants that carry one — tool_call/plan/etc. updates never do.
2528
+ * No-op when `messageId` is falsy, so callers can pass it through unconditionally.
2529
+ */
2530
+ function applyMessageId(update, messageId) {
2531
+ if (messageId &&
2532
+ (update.sessionUpdate === "agent_message_chunk" ||
2533
+ update.sessionUpdate === "user_message_chunk" ||
2534
+ update.sessionUpdate === "agent_thought_chunk")) {
2535
+ update.messageId = messageId;
2536
+ }
2537
+ }
2386
2538
  /**
2387
2539
  * Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
2388
2540
  * Only handles text, image, and thinking chunks for now.
@@ -2399,6 +2551,7 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
2399
2551
  text: content,
2400
2552
  },
2401
2553
  };
2554
+ applyMessageId(update, options?.messageId);
2402
2555
  if (options?.parentToolUseId) {
2403
2556
  update._meta = {
2404
2557
  ...update._meta,
@@ -2471,40 +2624,40 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
2471
2624
  else {
2472
2625
  // Only register hooks on first encounter to avoid double-firing
2473
2626
  if (registerHooks && !alreadyCached) {
2627
+ // Capture the tool name in the closure rather than re-reading the
2628
+ // cache when the hook fires. The cache entry is pruned at
2629
+ // tool_result time, and a PostToolUse hook can fire after that, so
2630
+ // closing over the name keeps the diff working without depending on
2631
+ // (or pinning) the cache entry's lifetime.
2632
+ const toolName = chunk.name;
2474
2633
  registerHookCallback(chunk.id, {
2475
2634
  onPostToolUseHook: async (toolUseId, toolInput, toolResponse) => {
2476
- const toolUse = toolUseCache[toolUseId];
2477
- if (toolUse) {
2478
- // Both `Edit` and `Write` produce a structuredPatch in their
2479
- // PostToolUse tool_response. For Edit the diff replaces the
2480
- // optimistic content built at tool_use time. For Write the
2481
- // optimistic content (built from `input.content` alone with
2482
- // `oldText: null`) shows "creation" semantics regardless of
2483
- // whether the file existed; the structuredPatch from the
2484
- // hook lets us emit the real diff for `type: "update"`. The
2485
- // helper returns `{}` if the response shape isn't usable.
2486
- const editDiff = toolUse.name === "Edit" || toolUse.name === "Write"
2487
- ? toolUpdateFromDiffToolResponse(toolResponse)
2488
- : {};
2489
- const update = {
2490
- _meta: {
2491
- claudeCode: {
2492
- toolResponse,
2493
- toolName: toolUse.name,
2494
- },
2635
+ // Both `Edit` and `Write` produce a structuredPatch in their
2636
+ // PostToolUse tool_response. For Edit the diff replaces the
2637
+ // optimistic content built at tool_use time. For Write the
2638
+ // optimistic content (built from `input.content` alone with
2639
+ // `oldText: null`) shows "creation" semantics regardless of
2640
+ // whether the file existed; the structuredPatch from the
2641
+ // hook lets us emit the real diff for `type: "update"`. The
2642
+ // helper returns `{}` if the response shape isn't usable.
2643
+ const editDiff = toolName === "Edit" || toolName === "Write"
2644
+ ? toolUpdateFromDiffToolResponse(toolResponse)
2645
+ : {};
2646
+ const update = {
2647
+ _meta: {
2648
+ claudeCode: {
2649
+ toolResponse,
2650
+ toolName,
2495
2651
  },
2496
- toolCallId: toolUseId,
2497
- sessionUpdate: "tool_call_update",
2498
- ...editDiff,
2499
- };
2500
- await client.sessionUpdate({
2501
- sessionId,
2502
- update,
2503
- });
2504
- }
2505
- else {
2506
- logger.error(`[claude-agent-acp] Got a tool response for tool use that wasn't tracked: ${toolUseId}`);
2507
- }
2652
+ },
2653
+ toolCallId: toolUseId,
2654
+ sessionUpdate: "tool_call_update",
2655
+ ...editDiff,
2656
+ };
2657
+ await client.sessionUpdate({
2658
+ sessionId,
2659
+ update,
2660
+ });
2508
2661
  },
2509
2662
  });
2510
2663
  }
@@ -2627,6 +2780,11 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
2627
2780
  ...toolUpdate,
2628
2781
  };
2629
2782
  }
2783
+ // The tool_use is fully resolved now — drop it so a long session doesn't
2784
+ // retain every tool call. The PostToolUse hook (Edit/Write diffs) closes
2785
+ // over the tool name and no longer reads the cache, so pruning here is
2786
+ // safe regardless of hook/result ordering.
2787
+ delete toolUseCache[chunk.tool_use_id];
2630
2788
  break;
2631
2789
  }
2632
2790
  case "document":
@@ -2655,6 +2813,7 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
2655
2813
  },
2656
2814
  };
2657
2815
  }
2816
+ applyMessageId(update, options?.messageId);
2658
2817
  output.push({ sessionId, update });
2659
2818
  }
2660
2819
  }
@@ -2669,6 +2828,7 @@ export function streamEventToAcpNotifications(message, sessionId, toolUseCache,
2669
2828
  parentToolUseId: message.parent_tool_use_id,
2670
2829
  cwd: options?.cwd,
2671
2830
  taskState: options?.taskState,
2831
+ messageId: options?.messageId,
2672
2832
  });
2673
2833
  case "content_block_delta":
2674
2834
  return toAcpNotifications([event.delta], "assistant", sessionId, toolUseCache, client, logger, {
@@ -2676,6 +2836,7 @@ export function streamEventToAcpNotifications(message, sessionId, toolUseCache,
2676
2836
  parentToolUseId: message.parent_tool_use_id,
2677
2837
  cwd: options?.cwd,
2678
2838
  taskState: options?.taskState,
2839
+ messageId: options?.messageId,
2679
2840
  });
2680
2841
  // No content. `ping` is a Messages-API keep-alive event that the SDK's
2681
2842
  // `BetaRawMessageStreamEvent` union doesn't include even though the
@@ -2711,7 +2872,8 @@ function commonPrefixLength(a, b) {
2711
2872
  return i;
2712
2873
  }
2713
2874
  /** Best-effort first guess of a model's context window from its ID, used only
2714
- * until a `result` message arrives with the authoritative `modelUsage` value.
2875
+ * as a fallback when the SDK's authoritative `getContextUsage` is unavailable
2876
+ * (and until a `result` message arrives with the `modelUsage` value).
2715
2877
  * Anthropic 1M-context variants encode "1m" as a distinct token in the SDK
2716
2878
  * model ID (e.g., "claude-opus-4-6-1m"), which `\b1m\b` catches without also
2717
2879
  * matching things like "10m" or embedded substrings. */
@@ -2720,6 +2882,42 @@ function inferContextWindowFromModel(model) {
2720
2882
  return 1_000_000;
2721
2883
  return null;
2722
2884
  }
2885
+ /** Fetch the SDK's authoritative context-window occupancy via the
2886
+ * `getContextUsage` control request. Unlike the per-message API usage numbers
2887
+ * (which only count message tokens), this `totalTokens` includes the system
2888
+ * prompt, tool schemas, MCP tools, and memory-file overhead — the real
2889
+ * occupancy the user sees. Returns `null` on any control-request failure.
2890
+ *
2891
+ * Note: we deliberately do NOT use this response's window fields for `size`.
2892
+ * They have been observed to under-report extended (1M) context windows, so
2893
+ * the window keeps coming from `modelUsage` / `inferContextWindowFromModel`,
2894
+ * which handle the 1M variants correctly. */
2895
+ async function fetchContextUsedTokens(query, logger) {
2896
+ try {
2897
+ const usage = await query.getContextUsage();
2898
+ return usage.totalTokens;
2899
+ }
2900
+ catch (error) {
2901
+ logger.error("Failed to fetch context usage from SDK:", error);
2902
+ return null;
2903
+ }
2904
+ }
2905
+ /** Translate the legacy `MAX_THINKING_TOKENS` env var into the SDK's `thinking`
2906
+ * option. The `maxThinkingTokens` option it used to feed is deprecated and
2907
+ * reduced to on/off on current models, so map the value to explicit thinking
2908
+ * config instead: unset → `undefined` (SDK default, adaptive on models that
2909
+ * support it); `0` → disabled; a positive integer → a fixed token budget.
2910
+ * Anything else is ignored with a warning. */
2911
+ function resolveThinkingConfig(raw, logger) {
2912
+ if (raw === undefined)
2913
+ return undefined;
2914
+ const parsed = Number.parseInt(raw, 10);
2915
+ if (Number.isNaN(parsed) || parsed < 0) {
2916
+ logger.error(`Ignoring MAX_THINKING_TOKENS: expected a non-negative integer, got '${raw}'.`);
2917
+ return undefined;
2918
+ }
2919
+ return parsed === 0 ? { type: "disabled" } : { type: "enabled", budgetTokens: parsed };
2920
+ }
2723
2921
  function parseModelConfig(raw) {
2724
2922
  if (!raw)
2725
2923
  return undefined;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.41.0",
6
+ "version": "0.42.0",
7
7
  "description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
8
8
  "main": "dist/lib.js",
9
9
  "types": "dist/lib.d.ts",
@@ -61,7 +61,7 @@
61
61
  "license": "Apache-2.0",
62
62
  "dependencies": {
63
63
  "@agentclientprotocol/sdk": "0.24.0",
64
- "@anthropic-ai/claude-agent-sdk": "0.3.162",
64
+ "@anthropic-ai/claude-agent-sdk": "0.3.165",
65
65
  "zod": "^3.25.0 || ^4.0.0"
66
66
  },
67
67
  "devDependencies": {