@agentclientprotocol/claude-agent-acp 0.41.0 → 0.43.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;
@@ -215,7 +227,7 @@ export declare class ClaudeAcpAgent implements Agent {
215
227
  /** Tear down all active sessions. Called when the ACP connection closes. */
216
228
  dispose(): Promise<void>;
217
229
  closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse>;
218
- unstable_deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse>;
230
+ deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse>;
219
231
  setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse>;
220
232
  setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse>;
221
233
  private applySessionMode;
@@ -223,13 +235,48 @@ export declare class ClaudeAcpAgent implements Agent {
223
235
  readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse>;
224
236
  writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
225
237
  canUseTool(sessionId: string): CanUseTool;
238
+ /**
239
+ * Handle elicitation requests that originate from MCP servers by forwarding
240
+ * them to the client over ACP. Modes the client did not advertise (or
241
+ * requests we can't represent) are declined.
242
+ */
243
+ private handleMcpElicitation;
244
+ /**
245
+ * Present the built-in AskUserQuestion tool's questions as an ACP form
246
+ * elicitation and return the answers as the tool's `updatedInput`. Called from
247
+ * `canUseTool` since that is where the SDK routes the tool's permission check.
248
+ */
249
+ private handleAskUserQuestion;
226
250
  private sendAvailableCommandsUpdate;
227
251
  private updateConfigOption;
228
252
  private applyConfigOptionValue;
229
253
  private getOrCreateSession;
254
+ /**
255
+ * Ensures the requested `cwd` is an absolute path that points at an existing
256
+ * directory before we create a session. Throws an `invalidParams` error with
257
+ * an actionable message so clients (e.g. Zed) can surface it to the user
258
+ * instead of failing later with an opaque SDK error.
259
+ */
260
+ private validateCwd;
230
261
  private createSession;
231
262
  }
232
263
  export declare function promptToClaude(prompt: PromptRequest): SDKUserMessage;
264
+ /**
265
+ * Resolves the ACP `messageId` for a Claude SDK message (live) or a persisted
266
+ * transcript message (replay) so chunk grouping is identical in both views.
267
+ *
268
+ * Assistant turns are keyed by the Anthropic API message id (`message.id`),
269
+ * which is identical at `message_start`, on the consolidated assistant message,
270
+ * and in the persisted transcript — unlike the per-`stream_event` uuid, which is
271
+ * unique per event and never persisted. User messages have no API id, but they
272
+ * are never streamed, so their (stable) SDK uuid is used instead. ACP message
273
+ * ids are opaque strings, so no particular format is required.
274
+ */
275
+ export declare function messageIdForGrouping(message: {
276
+ type?: string;
277
+ uuid?: string | null;
278
+ message?: unknown;
279
+ }): string | undefined;
233
280
  /**
234
281
  * Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
235
282
  * Only handles text, image, and thinking chunks for now.
@@ -240,11 +287,13 @@ export declare function toAcpNotifications(content: string | ContentBlockParam[]
240
287
  parentToolUseId?: string | null;
241
288
  cwd?: string;
242
289
  taskState?: TaskState;
290
+ messageId?: string;
243
291
  }): SessionNotification[];
244
292
  export declare function streamEventToAcpNotifications(message: SDKPartialAssistantMessage, sessionId: string, toolUseCache: ToolUseCache, client: AgentSideConnection, logger: Logger, options?: {
245
293
  clientCapabilities?: ClientCapabilities;
246
294
  cwd?: string;
247
295
  taskState?: TaskState;
296
+ messageId?: string;
248
297
  }): SessionNotification[];
249
298
  export declare function runAcp(): {
250
299
  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
@@ -1,9 +1,11 @@
1
1
  import { AgentSideConnection, ndJsonStream, RequestError, } from "@agentclientprotocol/sdk";
2
2
  import { deleteSession, getSessionMessages, listSessions, query, } from "@anthropic-ai/claude-agent-sdk";
3
3
  import { randomUUID } from "node:crypto";
4
+ import * as fs from "node:fs/promises";
4
5
  import * as os from "node:os";
5
6
  import * as path from "node:path";
6
7
  import packageJson from "../package.json" with { type: "json" };
8
+ import { applyAskElicitationResponse, askUserQuestionsToCreateRequest, createElicitationResponseToElicitResult, extractAskUserQuestions, mcpElicitationToCreateRequest, } from "./elicitation.js";
7
9
  import { SettingsManager } from "./settings.js";
8
10
  import { applyTaskCreate, applyTaskUpdate, createPostToolUseHook, createTaskHook, parseTaskCreateOutput, planEntries, registerHookCallback, taskStateToPlanEntries, toolInfoFromToolUse, toolUpdateFromDiffToolResponse, toolUpdateFromToolResult, } from "./tools.js";
9
11
  import { nodeToWebReadable, nodeToWebWritable, Pushable, unreachable } from "./utils.js";
@@ -243,14 +245,12 @@ export function describeAlwaysAllow(suggestions, toolName) {
243
245
  // Implement the ACP Agent interface
244
246
  export class ClaudeAcpAgent {
245
247
  constructor(client, logger) {
246
- this.backgroundTerminals = {};
247
248
  /** Grace period before a `session/cancel` forces a wedged prompt loop to
248
249
  * return "cancelled". See {@link DEFAULT_FORCE_CANCEL_GRACE_MS}. Mutable so
249
250
  * tests can shrink it. */
250
251
  this.forceCancelGraceMs = DEFAULT_FORCE_CANCEL_GRACE_MS;
251
252
  this.sessions = {};
252
253
  this.client = client;
253
- this.toolUseCache = {};
254
254
  this.logger = logger ?? console;
255
255
  }
256
256
  async initialize(request) {
@@ -490,6 +490,32 @@ export class ClaudeAcpAgent {
490
490
  // `compacting` status sets it again, so every distinct compaction (e.g.
491
491
  // repeated auto-compactions in a long turn) is still shown.
492
492
  let compactionInProgress = false;
493
+ // Holds the Anthropic API message id of the assistant message currently
494
+ // being streamed, captured from `message_start` so every streamed chunk can
495
+ // be tagged with it. We use the API message id rather than the
496
+ // per-`stream_event` uuid because the same id is also present on the
497
+ // consolidated assistant message and in the persisted transcript — so a turn
498
+ // keeps the same ACP `messageId` whether it is streamed live or replayed
499
+ // from history. The per-event uuid is unique per event and never persisted.
500
+ // A single value suffices because every streaming partial arrives with
501
+ // `parent_tool_use_id === null` (subagent work is folded into tool-result
502
+ // messages, never surfaced as partial streams).
503
+ let currentStreamMessageId;
504
+ // Per-message-id record of which assistant content actually streamed live via
505
+ // `stream_event` deltas, split by block type. The `assistant` case below
506
+ // normally drops `text`/`thinking` blocks on the assumption they already
507
+ // reached the client as `agent_message_chunk`/`agent_thought_chunk`. That
508
+ // breaks behind Anthropic-protocol gateways that return a turn as a single
509
+ // non-streamed block (common with OpenAI-compatible proxies): no
510
+ // `content_block_delta` fires, so the assembled block is the only copy the
511
+ // client will ever see. We keep the filter per (id, block type) for content
512
+ // that did stream, and fall back to forwarding what did not. Split by type so
513
+ // a gateway that streams text but not thinking (or vice versa) doesn't lose
514
+ // the un-streamed block. Only top-level (`parent_tool_use_id === null`)
515
+ // streams are recorded — subagent text is never streamed and must stay
516
+ // filtered, as it is internal to the tool call.
517
+ const streamedTextIds = new Set();
518
+ const streamedThinkingIds = new Set();
493
519
  const userMessage = promptToClaude(params);
494
520
  const promptUuid = randomUUID();
495
521
  userMessage.uuid = promptUuid;
@@ -591,28 +617,35 @@ export class ClaudeAcpAgent {
591
617
  break;
592
618
  }
593
619
  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.
620
+ // Refresh the displayed usage immediately so the client doesn't
621
+ // keep showing the stale pre-compaction size (e.g. "944k/1m")
622
+ // right after the user sees "Compacting completed", which is
623
+ // confusing and wrong.
596
624
  //
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.
625
+ // Prefer the SDK's authoritative post-compaction `used` via
626
+ // getContextUsage — it reflects the real retained context
627
+ // (system prompt + tools + surviving messages), which the
628
+ // per-message API usage numbers can't give us until the next
629
+ // turn's result. If the control request fails, fall back to the
630
+ // used:0 approximation: directionally correct (context just
631
+ // dropped dramatically) and replaced within seconds by the next
632
+ // result message.
633
+ //
634
+ // `size` keeps coming from session.contextWindowSize (learned
635
+ // from modelUsage / the model heuristic) — getContextUsage's
636
+ // window field under-reports extended 1M windows.
605
637
  //
606
638
  // The "Compacting completed." text is emitted from the `status`
607
639
  // handler (keyed on `compact_result`), not here, so the failure
608
640
  // path gets a message too.
609
- lastAssistantTotalUsage = 0;
641
+ const usedTokens = await fetchContextUsedTokens(session.query, this.logger);
610
642
  lastAssistantUsage = null;
643
+ lastAssistantTotalUsage = usedTokens ?? 0;
611
644
  await this.client.sessionUpdate({
612
645
  sessionId: message.session_id,
613
646
  update: {
614
647
  sessionUpdate: "usage_update",
615
- used: 0,
648
+ used: lastAssistantTotalUsage,
616
649
  size: session.contextWindowSize,
617
650
  },
618
651
  });
@@ -690,6 +723,47 @@ export class ClaudeAcpAgent {
690
723
  });
691
724
  break;
692
725
  }
726
+ case "mirror_error": {
727
+ // The SDK failed to persist session history (SessionStore
728
+ // append rejected/timed out after retry) — potential data loss
729
+ // the user should know about rather than a silent gap on
730
+ // resume. Log it and surface a warning in the conversation.
731
+ this.logger.error(`Session ${message.session_id}: failed to persist history: ${message.error}`);
732
+ break;
733
+ }
734
+ case "permission_denied": {
735
+ // A tool call was auto-denied (by a rule, the classifier,
736
+ // dontAsk mode, etc.) before running. The tool_use block was
737
+ // already emitted as a `tool_call`, so mark it failed with the
738
+ // rejection reason — otherwise the client shows a tool call
739
+ // that silently never resolves.
740
+ const reason = message.decision_reason ?? message.message;
741
+ await this.client.sessionUpdate({
742
+ sessionId: message.session_id,
743
+ update: {
744
+ sessionUpdate: "tool_call_update",
745
+ toolCallId: message.tool_use_id,
746
+ status: "failed",
747
+ content: [
748
+ {
749
+ type: "content",
750
+ content: { type: "text", text: `Permission denied: ${reason}` },
751
+ },
752
+ ],
753
+ _meta: {
754
+ claudeCode: {
755
+ toolName: message.tool_name,
756
+ toolResponse: {
757
+ decisionReasonType: message.decision_reason_type,
758
+ decisionReason: message.decision_reason,
759
+ message: message.message,
760
+ },
761
+ },
762
+ },
763
+ },
764
+ });
765
+ break;
766
+ }
693
767
  case "hook_started":
694
768
  case "hook_progress":
695
769
  case "hook_response":
@@ -698,12 +772,26 @@ export class ClaudeAcpAgent {
698
772
  case "task_notification":
699
773
  case "task_progress":
700
774
  case "task_updated":
701
- case "elicitation_complete":
775
+ break;
776
+ case "elicitation_complete": {
777
+ // A url-mode MCP elicitation finished server-side. Let the client
778
+ // dismiss any UI it opened for it. Only meaningful when the
779
+ // client supports url elicitation; ignore failures otherwise.
780
+ if (this.clientCapabilities?.elicitation?.url) {
781
+ try {
782
+ await this.client.unstable_completeElicitation({
783
+ elicitationId: message.elicitation_id,
784
+ });
785
+ }
786
+ catch (error) {
787
+ this.logger.error(`Failed to complete elicitation: ${error}`);
788
+ }
789
+ }
790
+ break;
791
+ }
702
792
  case "plugin_install":
703
793
  case "notification":
704
794
  case "api_retry":
705
- case "mirror_error":
706
- case "permission_denied":
707
795
  case "thinking_tokens":
708
796
  // Todo: process via status api: https://docs.claude.com/en/docs/claude-code/hooks#hook-output
709
797
  break;
@@ -795,7 +883,7 @@ export class ClaudeAcpAgent {
795
883
  // Task-notification followups never originate from a user
796
884
  // slash command, so skip the forwarding for them.
797
885
  if (isLocalOnlyCommand && !isTaskNotification) {
798
- for (const notification of toAcpNotifications(message.result, "assistant", params.sessionId, this.toolUseCache, this.client, this.logger)) {
886
+ for (const notification of toAcpNotifications(message.result, "assistant", params.sessionId, session.toolUseCache, this.client, this.logger)) {
799
887
  await this.client.sessionUpdate(notification);
800
888
  }
801
889
  }
@@ -833,6 +921,27 @@ export class ClaudeAcpAgent {
833
921
  break;
834
922
  }
835
923
  case "stream_event": {
924
+ // `message_start` carries the Anthropic API message id; capture it
925
+ // so the streamed chunks that follow (whose delta events don't carry
926
+ // it) can all be tagged with the same, replay-stable id.
927
+ if (message.event.type === "message_start") {
928
+ currentStreamMessageId = message.event.message.id || undefined;
929
+ }
930
+ // Record that this top-level message id actually streamed text/
931
+ // thinking, so the `assistant` case below knows its assembled blocks
932
+ // are duplicates (filter them) rather than the only copy (forward
933
+ // them). Gated on `parent_tool_use_id === null` so a subagent stream
934
+ // can't attribute its content to the top-level message id.
935
+ if (currentStreamMessageId &&
936
+ message.parent_tool_use_id === null &&
937
+ message.event.type === "content_block_delta") {
938
+ if (message.event.delta.type === "text_delta") {
939
+ streamedTextIds.add(currentStreamMessageId);
940
+ }
941
+ else if (message.event.delta.type === "thinking_delta") {
942
+ streamedThinkingIds.add(currentStreamMessageId);
943
+ }
944
+ }
836
945
  if (message.parent_tool_use_id === null &&
837
946
  (message.event.type === "message_start" || message.event.type === "message_delta")) {
838
947
  if (message.event.type === "message_start") {
@@ -880,10 +989,11 @@ export class ClaudeAcpAgent {
880
989
  });
881
990
  }
882
991
  }
883
- for (const notification of streamEventToAcpNotifications(message, params.sessionId, this.toolUseCache, this.client, this.logger, {
992
+ for (const notification of streamEventToAcpNotifications(message, params.sessionId, session.toolUseCache, this.client, this.logger, {
884
993
  clientCapabilities: this.clientCapabilities,
885
994
  cwd: session.cwd,
886
995
  taskState: session.taskState,
996
+ messageId: currentStreamMessageId,
887
997
  })) {
888
998
  await this.client.sessionUpdate(notification);
889
999
  }
@@ -894,6 +1004,14 @@ export class ClaudeAcpAgent {
894
1004
  if (session.cancelled) {
895
1005
  break;
896
1006
  }
1007
+ // Record the ACP messageId -> SDK uuid mapping for this message. The
1008
+ // consolidated message carries both ids, so this is where we learn
1009
+ // the uuid that the SDK's rewind/resume APIs key on for the id we
1010
+ // hand clients. Not read yet (see Session.messageIdToUuid).
1011
+ const mappedMessageId = messageIdForGrouping(message);
1012
+ if (mappedMessageId && typeof message.uuid === "string" && message.uuid.length > 0) {
1013
+ session.messageIdToUuid.set(mappedMessageId, message.uuid);
1014
+ }
897
1015
  // Check for prompt replay
898
1016
  if (message.type === "user" && "uuid" in message && message.uuid) {
899
1017
  if (message.uuid === promptUuid) {
@@ -940,11 +1058,12 @@ export class ClaudeAcpAgent {
940
1058
  message.message.content.includes("<local-command-stdout>")) {
941
1059
  const stripped = stripLocalCommandMetadata(message.message.content);
942
1060
  if (typeof stripped === "string") {
943
- for (const notification of toAcpNotifications(stripped, message.message.role, params.sessionId, this.toolUseCache, this.client, this.logger, {
1061
+ for (const notification of toAcpNotifications(stripped, message.message.role, params.sessionId, session.toolUseCache, this.client, this.logger, {
944
1062
  clientCapabilities: this.clientCapabilities,
945
1063
  parentToolUseId: message.parent_tool_use_id,
946
1064
  cwd: session.cwd,
947
1065
  taskState: session.taskState,
1066
+ messageId: messageIdForGrouping(message),
948
1067
  })) {
949
1068
  await this.client.sessionUpdate(notification);
950
1069
  }
@@ -978,25 +1097,91 @@ export class ClaudeAcpAgent {
978
1097
  message.message.content[0].text.includes("Please run /login")) {
979
1098
  throw RequestError.authRequired();
980
1099
  }
981
- const content = message.type === "assistant"
982
- ? // Handled by stream events above
983
- message.message.content.filter((item) => !["text", "thinking"].includes(item.type))
984
- : message.message.content;
985
- for (const notification of toAcpNotifications(content, message.message.role, params.sessionId, this.toolUseCache, this.client, this.logger, {
1100
+ let content;
1101
+ if (message.type === "assistant" && message.parent_tool_use_id === null) {
1102
+ // Top-level assistant message: drop text/thinking blocks already
1103
+ // streamed live as chunks, and forward (as a fallback) any that were
1104
+ // not, so non-streaming gateways still deliver the final answer.
1105
+ const id = messageIdForGrouping(message);
1106
+ content = message.message.content.filter((item) => {
1107
+ // Non-text blocks (tool_use, etc.) always pass through; their own
1108
+ // dedupe (`toolUseCache`) collapses the streamed/assembled pair.
1109
+ if (item.type !== "text" && item.type !== "thinking") {
1110
+ return true;
1111
+ }
1112
+ // Already delivered live as a chunk of this exact type — drop the
1113
+ // duplicate. Checked per type so streaming one (e.g. text) doesn't
1114
+ // suppress an un-streamed block of the other (e.g. thinking).
1115
+ const streamedLive = id !== undefined &&
1116
+ (item.type === "text" ? streamedTextIds : streamedThinkingIds).has(id);
1117
+ if (streamedLive) {
1118
+ return false;
1119
+ }
1120
+ // Empty assembled blocks carry nothing (some gateways emit an empty
1121
+ // `thinking` block before the real text) — don't forward stray
1122
+ // empty chunks.
1123
+ const text = item.type === "text" ? item.text : item.thinking;
1124
+ if (text.length === 0) {
1125
+ return false;
1126
+ }
1127
+ return true;
1128
+ });
1129
+ }
1130
+ else if (message.type === "assistant") {
1131
+ // Subagent assistant message (`parent_tool_use_id !== null`). It is
1132
+ // never streamed live and its text/thinking is internal to the tool
1133
+ // call — keep dropping it so subagent prose doesn't leak into the
1134
+ // top-level feed.
1135
+ content = message.message.content.filter((item) => item.type !== "text" && item.type !== "thinking");
1136
+ }
1137
+ else {
1138
+ content = message.message.content;
1139
+ }
1140
+ for (const notification of toAcpNotifications(content, message.message.role, params.sessionId, session.toolUseCache, this.client, this.logger, {
986
1141
  clientCapabilities: this.clientCapabilities,
987
1142
  parentToolUseId: message.parent_tool_use_id,
988
1143
  cwd: session.cwd,
989
1144
  taskState: session.taskState,
1145
+ messageId: messageIdForGrouping(message),
990
1146
  })) {
991
1147
  await this.client.sessionUpdate(notification);
992
1148
  }
993
1149
  break;
994
1150
  }
995
- case "tool_progress":
1151
+ case "tool_progress": {
1152
+ await this.client.sessionUpdate({
1153
+ sessionId: message.session_id,
1154
+ update: {
1155
+ sessionUpdate: "tool_call_update",
1156
+ toolCallId: message.tool_use_id,
1157
+ status: "in_progress",
1158
+ _meta: {
1159
+ claudeCode: {
1160
+ toolName: message.tool_name,
1161
+ toolResponse: { elapsedTimeSeconds: message.elapsed_time_seconds },
1162
+ },
1163
+ },
1164
+ },
1165
+ });
1166
+ break;
1167
+ }
1168
+ case "rate_limit_event": {
1169
+ if (lastAssistantTotalUsage !== null) {
1170
+ await this.client.sessionUpdate({
1171
+ sessionId: message.session_id,
1172
+ update: {
1173
+ sessionUpdate: "usage_update",
1174
+ used: lastAssistantTotalUsage,
1175
+ size: session.contextWindowSize,
1176
+ _meta: { "_claude/rateLimit": message.rate_limit_info },
1177
+ },
1178
+ });
1179
+ }
1180
+ break;
1181
+ }
996
1182
  case "tool_use_summary":
997
1183
  case "auth_status":
998
1184
  case "prompt_suggestion":
999
- case "rate_limit_event":
1000
1185
  break;
1001
1186
  default:
1002
1187
  unreachable(message);
@@ -1152,7 +1337,7 @@ export class ClaudeAcpAgent {
1152
1337
  await this.teardownSession(params.sessionId);
1153
1338
  return {};
1154
1339
  }
1155
- async unstable_deleteSession(params) {
1340
+ async deleteSession(params) {
1156
1341
  // Tear down any active in-memory state first so the on-disk file isn't
1157
1342
  // recreated by an outstanding query writing to it.
1158
1343
  if (this.sessions[params.sessionId]) {
@@ -1263,6 +1448,15 @@ export class ClaudeAcpAgent {
1263
1448
  const toolUseCache = {};
1264
1449
  const messages = await getSessionMessages(sessionId);
1265
1450
  for (const message of messages) {
1451
+ // Backfill the ACP messageId -> SDK uuid mapping for messages we didn't
1452
+ // observe live (resumed/loaded sessions), so rewind/resume can translate
1453
+ // a client-supplied id without an extra getSessionMessages read. Not read
1454
+ // yet (see Session.messageIdToUuid).
1455
+ const replayMessageId = messageIdForGrouping(message);
1456
+ const replaySession = this.sessions[sessionId];
1457
+ if (replaySession && replayMessageId && message.uuid) {
1458
+ replaySession.messageIdToUuid.set(replayMessageId, message.uuid);
1459
+ }
1266
1460
  // @ts-expect-error - untyped in SDK but we handle all of these
1267
1461
  let content = message.message.content;
1268
1462
  // @ts-expect-error - untyped in SDK but we handle all of these
@@ -1280,6 +1474,7 @@ export class ClaudeAcpAgent {
1280
1474
  clientCapabilities: this.clientCapabilities,
1281
1475
  cwd: this.sessions[sessionId]?.cwd,
1282
1476
  taskState: this.sessions[sessionId]?.taskState,
1477
+ messageId: replayMessageId,
1283
1478
  })) {
1284
1479
  await this.client.sessionUpdate(notification);
1285
1480
  }
@@ -1304,6 +1499,13 @@ export class ClaudeAcpAgent {
1304
1499
  message: "Session not found",
1305
1500
  };
1306
1501
  }
1502
+ // AskUserQuestion is surfaced to us as a normal permission check (the SDK
1503
+ // routes it through canUseTool whenever a callback is registered, rather
1504
+ // than the interactive dialog). Present it as an ACP form elicitation and
1505
+ // feed the answers back as updatedInput for the tool's own call() to read.
1506
+ if (toolName === "AskUserQuestion" && this.clientCapabilities?.elicitation?.form) {
1507
+ return this.handleAskUserQuestion(sessionId, toolInput, toolUseID, signal);
1508
+ }
1307
1509
  if (toolName === "ExitPlanMode") {
1308
1510
  const optionsAll = [
1309
1511
  { kind: "allow_always", name: 'Yes, and use "auto" mode', optionId: "auto" },
@@ -1429,6 +1631,62 @@ export class ClaudeAcpAgent {
1429
1631
  }
1430
1632
  };
1431
1633
  }
1634
+ /**
1635
+ * Handle elicitation requests that originate from MCP servers by forwarding
1636
+ * them to the client over ACP. Modes the client did not advertise (or
1637
+ * requests we can't represent) are declined.
1638
+ */
1639
+ handleMcpElicitation(sessionId, support) {
1640
+ return async (request, { signal }) => {
1641
+ const isUrl = request.mode === "url";
1642
+ if ((isUrl && !support.url) || (!isUrl && !support.form)) {
1643
+ return { action: "decline" };
1644
+ }
1645
+ const createRequest = mcpElicitationToCreateRequest(request, sessionId);
1646
+ if (!createRequest) {
1647
+ return { action: "decline" };
1648
+ }
1649
+ try {
1650
+ const response = await this.client.unstable_createElicitation(createRequest);
1651
+ if (signal.aborted) {
1652
+ return { action: "cancel" };
1653
+ }
1654
+ return createElicitationResponseToElicitResult(response);
1655
+ }
1656
+ catch (error) {
1657
+ this.logger.error(`Failed to forward MCP elicitation: ${error}`);
1658
+ return { action: "decline" };
1659
+ }
1660
+ };
1661
+ }
1662
+ /**
1663
+ * Present the built-in AskUserQuestion tool's questions as an ACP form
1664
+ * elicitation and return the answers as the tool's `updatedInput`. Called from
1665
+ * `canUseTool` since that is where the SDK routes the tool's permission check.
1666
+ */
1667
+ async handleAskUserQuestion(sessionId, toolInput, toolUseID, signal) {
1668
+ const questions = extractAskUserQuestions(toolInput);
1669
+ if (!questions) {
1670
+ return { behavior: "deny", message: "AskUserQuestion called with no valid questions." };
1671
+ }
1672
+ const createRequest = askUserQuestionsToCreateRequest(questions, sessionId, toolUseID);
1673
+ let response;
1674
+ try {
1675
+ response = await this.client.unstable_createElicitation(createRequest);
1676
+ }
1677
+ catch (error) {
1678
+ this.logger.error(`Failed to present AskUserQuestion elicitation: ${error}`);
1679
+ return { behavior: "deny", message: "Could not present the question to the user." };
1680
+ }
1681
+ if (signal.aborted) {
1682
+ throw new Error("Tool use aborted");
1683
+ }
1684
+ const outcome = applyAskElicitationResponse(response, toolInput, questions);
1685
+ if (outcome.action === "cancel") {
1686
+ throw new Error("Tool use aborted");
1687
+ }
1688
+ return { behavior: "allow", updatedInput: outcome.updatedInput };
1689
+ }
1432
1690
  async sendAvailableCommandsUpdate(sessionId) {
1433
1691
  const session = this.sessions[sessionId];
1434
1692
  if (!session)
@@ -1565,7 +1823,34 @@ export class ClaudeAcpAgent {
1565
1823
  configOptions: response.configOptions,
1566
1824
  };
1567
1825
  }
1826
+ /**
1827
+ * Ensures the requested `cwd` is an absolute path that points at an existing
1828
+ * directory before we create a session. Throws an `invalidParams` error with
1829
+ * an actionable message so clients (e.g. Zed) can surface it to the user
1830
+ * instead of failing later with an opaque SDK error.
1831
+ */
1832
+ async validateCwd(cwd) {
1833
+ if (!path.isAbsolute(cwd)) {
1834
+ throw RequestError.invalidParams({ cwd }, `\`cwd\` must be an absolute path, but received: ${cwd}`);
1835
+ }
1836
+ let stats;
1837
+ try {
1838
+ stats = await fs.stat(cwd);
1839
+ }
1840
+ catch {
1841
+ throw RequestError.invalidParams({ cwd }, `\`cwd\` does not exist on the machine running the agent: ${cwd}`);
1842
+ }
1843
+ if (!stats.isDirectory()) {
1844
+ throw RequestError.invalidParams({ cwd }, `\`cwd\` is not a directory: ${cwd}`);
1845
+ }
1846
+ }
1568
1847
  async createSession(params, creationOpts = {}) {
1848
+ // Validate `cwd` up front. The ACP spec requires an absolute path, and the
1849
+ // directory must actually exist on the machine running the agent. Without
1850
+ // this check a session is created against a missing directory and the
1851
+ // failure only surfaces later as a confusing "native binary failed to
1852
+ // launch" error from the SDK (see issue #749).
1853
+ await this.validateCwd(params.cwd);
1569
1854
  // We want to create a new session id unless it is resume,
1570
1855
  // but not resume + forkSession.
1571
1856
  let sessionId;
@@ -1631,14 +1916,21 @@ export class ClaudeAcpAgent {
1631
1916
  // Extract options from _meta if provided
1632
1917
  const sessionMeta = params._meta;
1633
1918
  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;
1919
+ // Configure thinking behavior from environment variable
1920
+ const thinking = resolveThinkingConfig(process.env.MAX_THINKING_TOKENS, this.logger);
1638
1921
  // Parse model configuration from environment (e.g. Bedrock model overrides)
1639
1922
  const modelConfig = parseModelConfig(process.env.CLAUDE_MODEL_CONFIG);
1640
- // Disable this for now, not a great way to expose this over ACP at the moment (in progress work so we can revisit)
1641
- const disallowedTools = ["AskUserQuestion"];
1923
+ // Elicitation modes the connected client advertised. We only forward
1924
+ // elicitations (and only re-enable AskUserQuestion) for modes the client
1925
+ // can actually render.
1926
+ const elicitationSupport = {
1927
+ form: !!this.clientCapabilities?.elicitation?.form,
1928
+ url: !!this.clientCapabilities?.elicitation?.url,
1929
+ };
1930
+ // AskUserQuestion surfaces as a `permission_ask_user_question` dialog that
1931
+ // we render as a form elicitation. Without form-elicitation support there
1932
+ // is no way to present it over ACP, so keep it disabled in that case.
1933
+ const disallowedTools = elicitationSupport.form ? [] : ["AskUserQuestion"];
1642
1934
  // Resolve which built-in tools to expose.
1643
1935
  // Explicit tools array from _meta.claudeCode.options takes precedence.
1644
1936
  // disableBuiltInTools is a legacy shorthand for tools: [] — kept for
@@ -1653,7 +1945,7 @@ export class ClaudeAcpAgent {
1653
1945
  const options = {
1654
1946
  systemPrompt,
1655
1947
  settingSources: ["user", "project", "local"],
1656
- ...(maxThinkingTokens !== undefined && { maxThinkingTokens }),
1948
+ ...(thinking !== undefined && { thinking }),
1657
1949
  ...userProvidedOptions,
1658
1950
  // CLAUDE_MODEL_CONFIG env var is a fallback for model
1659
1951
  // configuration (e.g. Bedrock model ID overrides). When the caller
@@ -1682,6 +1974,13 @@ export class ClaudeAcpAgent {
1682
1974
  allowDangerouslySkipPermissions: ALLOW_BYPASS,
1683
1975
  permissionMode,
1684
1976
  canUseTool: this.canUseTool(sessionId),
1977
+ // Forward MCP elicitation requests onto ACP elicitation. Only attached
1978
+ // when the client advertised support, so non-supporting clients keep the
1979
+ // SDK's default (auto-decline) behavior. (AskUserQuestion is handled in
1980
+ // canUseTool, not here.)
1981
+ ...(elicitationSupport.form || elicitationSupport.url
1982
+ ? { onElicitation: this.handleMcpElicitation(sessionId, elicitationSupport) }
1983
+ : {}),
1685
1984
  pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE ?? (await claudeCliPath()),
1686
1985
  extraArgs: {
1687
1986
  ...userProvidedOptions?.extraArgs,
@@ -1870,6 +2169,8 @@ export class ClaudeAcpAgent {
1870
2169
  emitRawSDKMessages: sessionMeta?.claudeCode?.emitRawSDKMessages ?? false,
1871
2170
  contextWindowSize: inferContextWindowFromModel(models.currentModelId) ?? DEFAULT_CONTEXT_WINDOW,
1872
2171
  taskState,
2172
+ toolUseCache: {},
2173
+ messageIdToUuid: new Map(),
1873
2174
  };
1874
2175
  return {
1875
2176
  sessionId,
@@ -2111,11 +2412,16 @@ function tokenizeModelPreference(model) {
2111
2412
  function scoreModelMatch(model, tokens, contextHint) {
2112
2413
  const haystack = `${model.value} ${model.displayName}`.toLowerCase();
2113
2414
  let score = 0;
2415
+ let nonHintMatched = false;
2114
2416
  for (const token of tokens) {
2115
2417
  if (haystack.includes(token)) {
2418
+ if (token !== contextHint)
2419
+ nonHintMatched = true;
2116
2420
  score += token === contextHint ? 3 : 1;
2117
2421
  }
2118
2422
  }
2423
+ if (contextHint && !nonHintMatched)
2424
+ return 0;
2119
2425
  return score;
2120
2426
  }
2121
2427
  function resolveModelPreference(models, preference) {
@@ -2383,6 +2689,42 @@ export function promptToClaude(prompt) {
2383
2689
  parent_tool_use_id: null,
2384
2690
  };
2385
2691
  }
2692
+ /**
2693
+ * Resolves the ACP `messageId` for a Claude SDK message (live) or a persisted
2694
+ * transcript message (replay) so chunk grouping is identical in both views.
2695
+ *
2696
+ * Assistant turns are keyed by the Anthropic API message id (`message.id`),
2697
+ * which is identical at `message_start`, on the consolidated assistant message,
2698
+ * and in the persisted transcript — unlike the per-`stream_event` uuid, which is
2699
+ * unique per event and never persisted. User messages have no API id, but they
2700
+ * are never streamed, so their (stable) SDK uuid is used instead. ACP message
2701
+ * ids are opaque strings, so no particular format is required.
2702
+ */
2703
+ export function messageIdForGrouping(message) {
2704
+ if (message.type === "assistant") {
2705
+ const inner = message.message;
2706
+ const apiId = inner && typeof inner === "object" && "id" in inner
2707
+ ? inner.id
2708
+ : undefined;
2709
+ if (typeof apiId === "string" && apiId.length > 0) {
2710
+ return apiId;
2711
+ }
2712
+ }
2713
+ return typeof message.uuid === "string" && message.uuid.length > 0 ? message.uuid : undefined;
2714
+ }
2715
+ /**
2716
+ * Stamps an ACP `messageId` onto a session update, but only on the message/
2717
+ * thought chunk variants that carry one — tool_call/plan/etc. updates never do.
2718
+ * No-op when `messageId` is falsy, so callers can pass it through unconditionally.
2719
+ */
2720
+ function applyMessageId(update, messageId) {
2721
+ if (messageId &&
2722
+ (update.sessionUpdate === "agent_message_chunk" ||
2723
+ update.sessionUpdate === "user_message_chunk" ||
2724
+ update.sessionUpdate === "agent_thought_chunk")) {
2725
+ update.messageId = messageId;
2726
+ }
2727
+ }
2386
2728
  /**
2387
2729
  * Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
2388
2730
  * Only handles text, image, and thinking chunks for now.
@@ -2399,6 +2741,7 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
2399
2741
  text: content,
2400
2742
  },
2401
2743
  };
2744
+ applyMessageId(update, options?.messageId);
2402
2745
  if (options?.parentToolUseId) {
2403
2746
  update._meta = {
2404
2747
  ...update._meta,
@@ -2471,40 +2814,40 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
2471
2814
  else {
2472
2815
  // Only register hooks on first encounter to avoid double-firing
2473
2816
  if (registerHooks && !alreadyCached) {
2817
+ // Capture the tool name in the closure rather than re-reading the
2818
+ // cache when the hook fires. The cache entry is pruned at
2819
+ // tool_result time, and a PostToolUse hook can fire after that, so
2820
+ // closing over the name keeps the diff working without depending on
2821
+ // (or pinning) the cache entry's lifetime.
2822
+ const toolName = chunk.name;
2474
2823
  registerHookCallback(chunk.id, {
2475
2824
  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
- },
2825
+ // Both `Edit` and `Write` produce a structuredPatch in their
2826
+ // PostToolUse tool_response. For Edit the diff replaces the
2827
+ // optimistic content built at tool_use time. For Write the
2828
+ // optimistic content (built from `input.content` alone with
2829
+ // `oldText: null`) shows "creation" semantics regardless of
2830
+ // whether the file existed; the structuredPatch from the
2831
+ // hook lets us emit the real diff for `type: "update"`. The
2832
+ // helper returns `{}` if the response shape isn't usable.
2833
+ const editDiff = toolName === "Edit" || toolName === "Write"
2834
+ ? toolUpdateFromDiffToolResponse(toolResponse)
2835
+ : {};
2836
+ const update = {
2837
+ _meta: {
2838
+ claudeCode: {
2839
+ toolResponse,
2840
+ toolName,
2495
2841
  },
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
- }
2842
+ },
2843
+ toolCallId: toolUseId,
2844
+ sessionUpdate: "tool_call_update",
2845
+ ...editDiff,
2846
+ };
2847
+ await client.sessionUpdate({
2848
+ sessionId,
2849
+ update,
2850
+ });
2508
2851
  },
2509
2852
  });
2510
2853
  }
@@ -2627,6 +2970,11 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
2627
2970
  ...toolUpdate,
2628
2971
  };
2629
2972
  }
2973
+ // The tool_use is fully resolved now — drop it so a long session doesn't
2974
+ // retain every tool call. The PostToolUse hook (Edit/Write diffs) closes
2975
+ // over the tool name and no longer reads the cache, so pruning here is
2976
+ // safe regardless of hook/result ordering.
2977
+ delete toolUseCache[chunk.tool_use_id];
2630
2978
  break;
2631
2979
  }
2632
2980
  case "document":
@@ -2655,6 +3003,7 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
2655
3003
  },
2656
3004
  };
2657
3005
  }
3006
+ applyMessageId(update, options?.messageId);
2658
3007
  output.push({ sessionId, update });
2659
3008
  }
2660
3009
  }
@@ -2669,6 +3018,7 @@ export function streamEventToAcpNotifications(message, sessionId, toolUseCache,
2669
3018
  parentToolUseId: message.parent_tool_use_id,
2670
3019
  cwd: options?.cwd,
2671
3020
  taskState: options?.taskState,
3021
+ messageId: options?.messageId,
2672
3022
  });
2673
3023
  case "content_block_delta":
2674
3024
  return toAcpNotifications([event.delta], "assistant", sessionId, toolUseCache, client, logger, {
@@ -2676,6 +3026,7 @@ export function streamEventToAcpNotifications(message, sessionId, toolUseCache,
2676
3026
  parentToolUseId: message.parent_tool_use_id,
2677
3027
  cwd: options?.cwd,
2678
3028
  taskState: options?.taskState,
3029
+ messageId: options?.messageId,
2679
3030
  });
2680
3031
  // No content. `ping` is a Messages-API keep-alive event that the SDK's
2681
3032
  // `BetaRawMessageStreamEvent` union doesn't include even though the
@@ -2711,7 +3062,8 @@ function commonPrefixLength(a, b) {
2711
3062
  return i;
2712
3063
  }
2713
3064
  /** 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.
3065
+ * as a fallback when the SDK's authoritative `getContextUsage` is unavailable
3066
+ * (and until a `result` message arrives with the `modelUsage` value).
2715
3067
  * Anthropic 1M-context variants encode "1m" as a distinct token in the SDK
2716
3068
  * model ID (e.g., "claude-opus-4-6-1m"), which `\b1m\b` catches without also
2717
3069
  * matching things like "10m" or embedded substrings. */
@@ -2720,6 +3072,42 @@ function inferContextWindowFromModel(model) {
2720
3072
  return 1_000_000;
2721
3073
  return null;
2722
3074
  }
3075
+ /** Fetch the SDK's authoritative context-window occupancy via the
3076
+ * `getContextUsage` control request. Unlike the per-message API usage numbers
3077
+ * (which only count message tokens), this `totalTokens` includes the system
3078
+ * prompt, tool schemas, MCP tools, and memory-file overhead — the real
3079
+ * occupancy the user sees. Returns `null` on any control-request failure.
3080
+ *
3081
+ * Note: we deliberately do NOT use this response's window fields for `size`.
3082
+ * They have been observed to under-report extended (1M) context windows, so
3083
+ * the window keeps coming from `modelUsage` / `inferContextWindowFromModel`,
3084
+ * which handle the 1M variants correctly. */
3085
+ async function fetchContextUsedTokens(query, logger) {
3086
+ try {
3087
+ const usage = await query.getContextUsage();
3088
+ return usage.totalTokens;
3089
+ }
3090
+ catch (error) {
3091
+ logger.error("Failed to fetch context usage from SDK:", error);
3092
+ return null;
3093
+ }
3094
+ }
3095
+ /** Translate the legacy `MAX_THINKING_TOKENS` env var into the SDK's `thinking`
3096
+ * option. The `maxThinkingTokens` option it used to feed is deprecated and
3097
+ * reduced to on/off on current models, so map the value to explicit thinking
3098
+ * config instead: unset → `undefined` (SDK default, adaptive on models that
3099
+ * support it); `0` → disabled; a positive integer → a fixed token budget.
3100
+ * Anything else is ignored with a warning. */
3101
+ function resolveThinkingConfig(raw, logger) {
3102
+ if (raw === undefined)
3103
+ return undefined;
3104
+ const parsed = Number.parseInt(raw, 10);
3105
+ if (Number.isNaN(parsed) || parsed < 0) {
3106
+ logger.error(`Ignoring MAX_THINKING_TOKENS: expected a non-negative integer, got '${raw}'.`);
3107
+ return undefined;
3108
+ }
3109
+ return parsed === 0 ? { type: "disabled" } : { type: "enabled", budgetTokens: parsed };
3110
+ }
2723
3111
  function parseModelConfig(raw) {
2724
3112
  if (!raw)
2725
3113
  return undefined;
@@ -0,0 +1,80 @@
1
+ import type { CreateElicitationRequest, CreateElicitationResponse } from "@agentclientprotocol/sdk";
2
+ import type { ElicitationRequest, ElicitationResult } from "@anthropic-ai/claude-agent-sdk";
3
+ import type { AskUserQuestionInput } from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
4
+ /**
5
+ * Bridges between the Claude Agent SDK's elicitation/dialog callbacks and ACP's
6
+ * (unstable) elicitation protocol.
7
+ *
8
+ * Two distinct SDK surfaces flow through here:
9
+ *
10
+ * 1. `onElicitation` — fired when an MCP server requests user input. These map
11
+ * directly onto ACP `session/create_elicitation` (form or url mode).
12
+ * 2. The built-in AskUserQuestion tool — when a `canUseTool` callback is
13
+ * registered the SDK routes its permission check through `canUseTool`
14
+ * (not the interactive `permission_ask_user_question` dialog). We render
15
+ * its questions as an ACP form elicitation and feed the user's selections
16
+ * back as the tool's `updatedInput`, which the tool's own `call()` reads.
17
+ */
18
+ /** Modes the connected client advertised support for. */
19
+ export type ElicitationSupport = {
20
+ form: boolean;
21
+ url: boolean;
22
+ };
23
+ /**
24
+ * Convert an MCP elicitation request (from the SDK's `onElicitation` callback)
25
+ * into an ACP `CreateElicitationRequest`. Returns `null` when the request can't
26
+ * be represented (e.g. a url-mode request with no url).
27
+ */
28
+ export declare function mcpElicitationToCreateRequest(request: ElicitationRequest, sessionId: string): CreateElicitationRequest | null;
29
+ /**
30
+ * Map an ACP elicitation response back to the MCP `ElicitResult` the SDK expects
31
+ * to hand back to the requesting server.
32
+ */
33
+ export declare function createElicitationResponseToElicitResult(response: CreateElicitationResponse): ElicitationResult;
34
+ /**
35
+ * A single question as supplied by the AskUserQuestion tool. Derived from the
36
+ * SDK's input type so the shape stays in sync; the SDK validates the model's
37
+ * tool call against this schema before it reaches us.
38
+ */
39
+ export type AskUserQuestion = AskUserQuestionInput["questions"][number];
40
+ /**
41
+ * Pull the well-formed questions out of an AskUserQuestion tool input. Returns
42
+ * `null` when there are no usable questions — including the case where every
43
+ * entry is malformed and filtering leaves an empty list — so callers can treat
44
+ * "nothing to ask" uniformly.
45
+ */
46
+ export declare function extractAskUserQuestions(input: Record<string, unknown>): AskUserQuestion[] | null;
47
+ /**
48
+ * Render the AskUserQuestion tool's questions as an ACP form elicitation.
49
+ *
50
+ * Fields are keyed by a short stable id (`question_<n>`) rather than the full
51
+ * question text, so the question text appears in exactly one place per field.
52
+ * Single-select questions use a titled `oneOf` enum; multi-select questions use
53
+ * an array with a titled `anyOf` item enum. The enum `const` is always the
54
+ * option label, since that is what the tool records as the answer.
55
+ *
56
+ * A trailing optional free-text field mirrors the CLI's custom-answer box: the
57
+ * user can type their own answer instead of (or as well as) picking an option.
58
+ * Nothing is marked required, so the user can also just skip — matching the
59
+ * built-in tool, which always offers Skip + a free-text box.
60
+ */
61
+ export declare function askUserQuestionsToCreateRequest(questions: AskUserQuestion[], sessionId: string, toolCallId: string | undefined): CreateElicitationRequest;
62
+ /** Outcome of an AskUserQuestion elicitation, decoupled from any transport. */
63
+ export type AskUserQuestionOutcome = {
64
+ action: "answered";
65
+ updatedInput: Record<string, unknown>;
66
+ } | {
67
+ action: "cancel";
68
+ };
69
+ /**
70
+ * Fold an ACP elicitation response into the AskUserQuestion tool's input.
71
+ *
72
+ * Selected labels are read back from the indexed form fields and written into
73
+ * `answers` as a `{ [questionText]: label }` map (comma-joining multi-selects)
74
+ * — the key shape the tool's own `call()` reads. Free text from the custom-
75
+ * answer field becomes the tool's top-level `response`. Decline yields empty
76
+ * answers (the model is told the user skipped rather than the turn aborting);
77
+ * cancel aborts the tool call.
78
+ */
79
+ export declare function applyAskElicitationResponse(response: CreateElicitationResponse, toolInput: Record<string, unknown>, questions: AskUserQuestion[]): AskUserQuestionOutcome;
80
+ //# sourceMappingURL=elicitation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elicitation.d.ts","sourceRoot":"","sources":["../src/elicitation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,wBAAwB,EACxB,yBAAyB,EAI1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,OAAO,KAAK,EACV,oBAAoB,EAErB,MAAM,6CAA6C,CAAC;AAErD;;;;;;;;;;;;;GAaG;AAEH,yDAAyD;AACzD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,kBAAkB,EAC3B,SAAS,EAAE,MAAM,GAChB,wBAAwB,GAAG,IAAI,CAyBjC;AAED;;;GAGG;AACH,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,yBAAyB,GAClC,iBAAiB,CAUnB;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,EAAE,GAAG,IAAI,CAUhG;AAUD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,eAAe,EAAE,EAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,wBAAwB,CAyC1B;AAED,+EAA+E;AAC/E,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC7D;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEzB;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,yBAAyB,EACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,SAAS,EAAE,eAAe,EAAE,GAC3B,sBAAsB,CAiCxB"}
@@ -0,0 +1,166 @@
1
+ import { randomUUID } from "node:crypto";
2
+ /**
3
+ * Convert an MCP elicitation request (from the SDK's `onElicitation` callback)
4
+ * into an ACP `CreateElicitationRequest`. Returns `null` when the request can't
5
+ * be represented (e.g. a url-mode request with no url).
6
+ */
7
+ export function mcpElicitationToCreateRequest(request, sessionId) {
8
+ if (request.mode === "url") {
9
+ if (!request.url) {
10
+ return null;
11
+ }
12
+ return {
13
+ mode: "url",
14
+ sessionId,
15
+ message: request.message,
16
+ url: request.url,
17
+ // URL elicitations need a stable id so the client can correlate the
18
+ // later `session/complete_elicitation` notification. MCP servers usually
19
+ // provide one; fall back to a generated id if not.
20
+ elicitationId: request.elicitationId ?? randomUUID(),
21
+ };
22
+ }
23
+ // Form mode (the default). The MCP `requestedSchema` is already a JSON Schema
24
+ // with primitive-typed properties, which is structurally what ACP expects.
25
+ return {
26
+ mode: "form",
27
+ sessionId,
28
+ message: request.message,
29
+ requestedSchema: normalizeElicitationSchema(request.requestedSchema),
30
+ };
31
+ }
32
+ /**
33
+ * Map an ACP elicitation response back to the MCP `ElicitResult` the SDK expects
34
+ * to hand back to the requesting server.
35
+ */
36
+ export function createElicitationResponseToElicitResult(response) {
37
+ switch (response.action) {
38
+ case "accept":
39
+ return { action: "accept", content: response.content ?? {} };
40
+ case "decline":
41
+ return { action: "decline" };
42
+ case "cancel":
43
+ default:
44
+ return { action: "cancel" };
45
+ }
46
+ }
47
+ /**
48
+ * Pull the well-formed questions out of an AskUserQuestion tool input. Returns
49
+ * `null` when there are no usable questions — including the case where every
50
+ * entry is malformed and filtering leaves an empty list — so callers can treat
51
+ * "nothing to ask" uniformly.
52
+ */
53
+ export function extractAskUserQuestions(input) {
54
+ const questions = input.questions;
55
+ if (!Array.isArray(questions)) {
56
+ return null;
57
+ }
58
+ const valid = questions.filter((q) => !!q && typeof q.question === "string" && Array.isArray(q.options) && q.options.length > 0);
59
+ return valid.length > 0 ? valid : null;
60
+ }
61
+ /** Stable form-field key for the question at the given index. */
62
+ function questionFieldKey(index) {
63
+ return `question_${index}`;
64
+ }
65
+ /** Form-field key for the optional free-text "custom answer" field. */
66
+ const CUSTOM_ANSWER_FIELD = "customAnswer";
67
+ /**
68
+ * Render the AskUserQuestion tool's questions as an ACP form elicitation.
69
+ *
70
+ * Fields are keyed by a short stable id (`question_<n>`) rather than the full
71
+ * question text, so the question text appears in exactly one place per field.
72
+ * Single-select questions use a titled `oneOf` enum; multi-select questions use
73
+ * an array with a titled `anyOf` item enum. The enum `const` is always the
74
+ * option label, since that is what the tool records as the answer.
75
+ *
76
+ * A trailing optional free-text field mirrors the CLI's custom-answer box: the
77
+ * user can type their own answer instead of (or as well as) picking an option.
78
+ * Nothing is marked required, so the user can also just skip — matching the
79
+ * built-in tool, which always offers Skip + a free-text box.
80
+ */
81
+ export function askUserQuestionsToCreateRequest(questions, sessionId, toolCallId) {
82
+ const single = questions.length === 1;
83
+ const properties = {};
84
+ questions.forEach((question, index) => {
85
+ const options = question.options.map((option) => ({
86
+ const: option.label,
87
+ title: option.description ? `${option.label} — ${option.description}` : option.label,
88
+ }));
89
+ // For a single question the prompt is carried by `message`, so we don't
90
+ // repeat it in the field description. With multiple questions each field
91
+ // needs its own question text.
92
+ const description = single ? undefined : question.question;
93
+ const title = question.header || undefined;
94
+ properties[questionFieldKey(index)] = question.multiSelect
95
+ ? { type: "array", title, description, items: { anyOf: options } }
96
+ : { type: "string", title, description, oneOf: options };
97
+ });
98
+ properties[CUSTOM_ANSWER_FIELD] = {
99
+ type: "string",
100
+ title: "Other",
101
+ description: "Type your own answer instead of choosing an option above (optional).",
102
+ };
103
+ const requestedSchema = {
104
+ type: "object",
105
+ properties,
106
+ };
107
+ const message = single ? questions[0].question : "Please answer the following questions.";
108
+ return {
109
+ mode: "form",
110
+ sessionId,
111
+ ...(toolCallId ? { toolCallId } : {}),
112
+ message,
113
+ requestedSchema,
114
+ };
115
+ }
116
+ /**
117
+ * Fold an ACP elicitation response into the AskUserQuestion tool's input.
118
+ *
119
+ * Selected labels are read back from the indexed form fields and written into
120
+ * `answers` as a `{ [questionText]: label }` map (comma-joining multi-selects)
121
+ * — the key shape the tool's own `call()` reads. Free text from the custom-
122
+ * answer field becomes the tool's top-level `response`. Decline yields empty
123
+ * answers (the model is told the user skipped rather than the turn aborting);
124
+ * cancel aborts the tool call.
125
+ */
126
+ export function applyAskElicitationResponse(response, toolInput, questions) {
127
+ if (response.action === "cancel") {
128
+ return { action: "cancel" };
129
+ }
130
+ if (response.action === "decline") {
131
+ return { action: "answered", updatedInput: { ...toolInput, answers: {} } };
132
+ }
133
+ const content = response.content ?? {};
134
+ // Typed against the tool's own output schema so the answer/response shapes
135
+ // stay in sync with what the built-in tool's call() expects to read back.
136
+ const answers = {};
137
+ questions.forEach((question, index) => {
138
+ const value = content[questionFieldKey(index)];
139
+ if (value === undefined || value === null) {
140
+ return;
141
+ }
142
+ const text = Array.isArray(value) ? value.join(", ") : String(value);
143
+ if (text === "") {
144
+ return;
145
+ }
146
+ answers[question.question] = text;
147
+ });
148
+ const updatedInput = { ...toolInput, answers };
149
+ const custom = content[CUSTOM_ANSWER_FIELD];
150
+ if (typeof custom === "string" && custom.trim() !== "") {
151
+ const response = custom;
152
+ updatedInput.response = response;
153
+ }
154
+ return { action: "answered", updatedInput };
155
+ }
156
+ /**
157
+ * Coerce an arbitrary MCP `requestedSchema` into an ACP `ElicitationSchema`.
158
+ * The two are structurally compatible JSON Schemas; we just guarantee the
159
+ * `type: "object"` discriminator is present.
160
+ */
161
+ function normalizeElicitationSchema(schema) {
162
+ if (!schema || typeof schema !== "object") {
163
+ return { type: "object", properties: {} };
164
+ }
165
+ return { ...schema, type: "object" };
166
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACT,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EASL,eAAe,EACf,gBAAgB,EAChB,eAAe,EAIhB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAGL,oBAAoB,EAEpB,6BAA6B,EAE9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEL,yCAAyC,EAGzC,qCAAqC,EAGrC,kCAAkC,EAGlC,+CAA+C,EAI/C,wBAAwB,EACxB,kCAAkC,EAIlC,gCAAgC,EAEhC,iCAAiC,EAClC,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AA0BxC,UAAU,QAAQ;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/B,KAAK,CAAC,EAAE;QACN,aAAa,CAAC,EAAE;YACd,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC;QACF,eAAe,CAAC,EAAE;YAChB,WAAW,EAAE,MAAM,CAAC;YACpB,IAAI,EAAE,MAAM,CAAC;SACd,CAAC;QACF,aAAa,CAAC,EAAE;YACd,WAAW,EAAE,MAAM,CAAC;YACpB,SAAS,EAAE,MAAM,CAAC;YAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;SACvB,CAAC;KACH,CAAC;CACH;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAQpE;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,GAAG,EACZ,sBAAsB,GAAE,OAAe,EACvC,GAAG,CAAC,EAAE,MAAM,GACX,QAAQ,CAkVV;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EACN,oBAAoB,GACpB,wBAAwB,GACxB,iCAAiC,GACjC,gCAAgC,GAChC,6BAA6B,GAC7B,qCAAqC,GACrC,yCAAyC,GACzC,+CAA+C,GAC/C,kCAAkC,GAClC,kCAAkC,EACtC,OAAO,EAAE,GAAG,GAAG,SAAS,EACxB,sBAAsB,GAAE,OAAe,GACtC,UAAU,CA6HZ;AA0GD,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;IAChD,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,GAAG,SAAS,GAAG,SAAS,EAAE,CAMxF;AAED;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AACF,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS,CAiCpF;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,eAAe,GAAG,SAAS,EAClC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GACnC,IAAI,CASN;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,CAiB1F;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,EAAE,CAMpE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQnD;AAeD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,OAAO,GAAG;IACrE,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC,CAoCA;AAcD,eAAO,MAAM,oBAAoB,GAC/B,WAAW,MAAM,EACjB,wBAEG;IACD,iBAAiB,CAAC,EAAE,CAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAClB,YAAY,EAAE,OAAO,KAClB,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,SAKF,CAAC;AAGF,eAAO,MAAM,qBAAqB,GAE9B,SAAQ,MAAgB,EACxB,UAAU;IACR,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,KACA,YAoBF,CAAC;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GACxB,SAAS;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,KAAG,YAsBpE,CAAC"}
package/dist/tools.js CHANGED
@@ -274,6 +274,22 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
274
274
  : [],
275
275
  };
276
276
  }
277
+ case "AskUserQuestion": {
278
+ const input = toolUse.input;
279
+ const questions = Array.isArray(input?.questions) ? input.questions : [];
280
+ return {
281
+ title: questions.length === 1 && questions[0]?.question
282
+ ? questions[0].question
283
+ : "Asking for your input",
284
+ kind: "other",
285
+ content: questions
286
+ .filter((q) => typeof q?.question === "string")
287
+ .map((q) => ({
288
+ type: "content",
289
+ content: { type: "text", text: q.question },
290
+ })),
291
+ };
292
+ }
277
293
  case "Other": {
278
294
  const input = toolUse.input;
279
295
  let output;
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.43.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",
@@ -60,14 +60,14 @@
60
60
  "author": "Zed Industries",
61
61
  "license": "Apache-2.0",
62
62
  "dependencies": {
63
- "@agentclientprotocol/sdk": "0.24.0",
64
- "@anthropic-ai/claude-agent-sdk": "0.3.162",
63
+ "@agentclientprotocol/sdk": "0.25.0",
64
+ "@anthropic-ai/claude-agent-sdk": "0.3.169",
65
65
  "zod": "^3.25.0 || ^4.0.0"
66
66
  },
67
67
  "devDependencies": {
68
- "@anthropic-ai/sdk": "0.100.1",
68
+ "@anthropic-ai/sdk": "0.102.0",
69
69
  "@eslint/js": "10.0.1",
70
- "@types/node": "25.9.1",
70
+ "@types/node": "25.9.2",
71
71
  "@typescript-eslint/eslint-plugin": "8.60.1",
72
72
  "@typescript-eslint/parser": "8.60.1",
73
73
  "eslint": "10.4.1",