@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.
- package/dist/acp-agent.d.ts +63 -14
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +455 -67
- package/dist/elicitation.d.ts +80 -0
- package/dist/elicitation.d.ts.map +1 -0
- package/dist/elicitation.js +166 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +16 -0
- package/package.json +5 -5
package/dist/acp-agent.d.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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;
|
package/dist/acp-agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,mBAAmB,EACnB,mBAAmB,EAGnB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EAEnB,iBAAiB,EACjB,kBAAkB,EAElB,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EAEpB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,6BAA6B,EAC7B,8BAA8B,EAC9B,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,
|
|
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
|
-
//
|
|
595
|
-
// the stale pre-compaction
|
|
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
|
-
//
|
|
598
|
-
//
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
//
|
|
604
|
-
//
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
|
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
|
|
1635
|
-
const
|
|
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
|
-
//
|
|
1641
|
-
|
|
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
|
-
...(
|
|
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
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
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
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
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
|
-
*
|
|
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
|
+
}
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
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.
|
|
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.
|
|
64
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
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.
|
|
68
|
+
"@anthropic-ai/sdk": "0.102.0",
|
|
69
69
|
"@eslint/js": "10.0.1",
|
|
70
|
-
"@types/node": "25.9.
|
|
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",
|