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