@agentclientprotocol/claude-agent-acp 0.39.0 → 0.41.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 +28 -2
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +151 -19
- package/package.json +8 -8
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,
|
|
1
|
+
import { Agent, AgentSideConnection, AuthenticateRequest, CancelNotification, ClientCapabilities, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, ListSessionsRequest, ListSessionsResponse, LoadSessionRequest, LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, ResumeSessionRequest, ResumeSessionResponse, SessionConfigOption, SessionModeState, SessionNotification, SetSessionConfigOptionRequest, SetSessionConfigOptionResponse, SetSessionModeRequest, SetSessionModeResponse, CloseSessionRequest, CloseSessionResponse, DeleteSessionRequest, DeleteSessionResponse, TerminalHandle, TerminalOutputResponse, WriteTextFileRequest, WriteTextFileResponse } from "@agentclientprotocol/sdk";
|
|
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";
|
|
@@ -19,6 +19,18 @@ type AccumulatedUsage = {
|
|
|
19
19
|
cachedReadTokens: number;
|
|
20
20
|
cachedWriteTokens: number;
|
|
21
21
|
};
|
|
22
|
+
/** Internal model-selection state. Mirrors the shape the ACP SDK exposed as
|
|
23
|
+
* `SessionModelState` before model selection moved entirely into
|
|
24
|
+
* `SessionConfigOption` (category "model"). Retained internally to track the
|
|
25
|
+
* current model and build the "model" config option. */
|
|
26
|
+
type SessionModelState = {
|
|
27
|
+
availableModels: Array<{
|
|
28
|
+
modelId: string;
|
|
29
|
+
name: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
}>;
|
|
32
|
+
currentModelId: string;
|
|
33
|
+
};
|
|
22
34
|
type Session = {
|
|
23
35
|
query: Query;
|
|
24
36
|
input: Pushable<SDKUserMessage>;
|
|
@@ -40,6 +52,17 @@ type Session = {
|
|
|
40
52
|
}>;
|
|
41
53
|
nextPendingOrder: number;
|
|
42
54
|
abortController: AbortController;
|
|
55
|
+
/** Per-turn signal the active prompt loop races `query.next()` against.
|
|
56
|
+
* Aborted by cancel() (after a grace period) to force the loop to return
|
|
57
|
+
* "cancelled" when the SDK is wedged and `query.next()` never yields again
|
|
58
|
+
* (issue #680). Distinct from `abortController`: this only wakes the loop;
|
|
59
|
+
* it does NOT touch the SDK query/subprocess. Undefined when no prompt is
|
|
60
|
+
* actively consuming the query. */
|
|
61
|
+
cancelController?: AbortController;
|
|
62
|
+
/** Pending grace-period timer that aborts `cancelController`. Cleared when
|
|
63
|
+
* the loop returns normally so the backstop never fires after a clean
|
|
64
|
+
* cancel. */
|
|
65
|
+
forceCancelTimer?: ReturnType<typeof setTimeout>;
|
|
43
66
|
emitRawSDKMessages: boolean | SDKMessageFilter[];
|
|
44
67
|
/** Context window size of the last top-level assistant model, carried across
|
|
45
68
|
* prompts so mid-stream usage_update notifications report a correct `size`
|
|
@@ -172,6 +195,10 @@ export declare class ClaudeAcpAgent implements Agent {
|
|
|
172
195
|
clientCapabilities?: ClientCapabilities;
|
|
173
196
|
logger: Logger;
|
|
174
197
|
gatewayAuthRequest?: GatewayAuthRequest;
|
|
198
|
+
/** Grace period before a `session/cancel` forces a wedged prompt loop to
|
|
199
|
+
* return "cancelled". See {@link DEFAULT_FORCE_CANCEL_GRACE_MS}. Mutable so
|
|
200
|
+
* tests can shrink it. */
|
|
201
|
+
forceCancelGraceMs: number;
|
|
175
202
|
constructor(client: AgentSideConnection, logger?: Logger);
|
|
176
203
|
initialize(request: InitializeRequest): Promise<InitializeResponse>;
|
|
177
204
|
newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
|
|
@@ -189,7 +216,6 @@ export declare class ClaudeAcpAgent implements Agent {
|
|
|
189
216
|
dispose(): Promise<void>;
|
|
190
217
|
closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse>;
|
|
191
218
|
unstable_deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse>;
|
|
192
|
-
unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse | void>;
|
|
193
219
|
setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse>;
|
|
194
220
|
setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse>;
|
|
195
221
|
private applySessionMode;
|
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,
|
|
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
|
@@ -27,6 +27,16 @@ const ZERO_USAGE = Object.freeze({
|
|
|
27
27
|
cache_creation_input_tokens: 0,
|
|
28
28
|
});
|
|
29
29
|
const DEFAULT_CONTEXT_WINDOW = 200000;
|
|
30
|
+
/** Floor after `session/cancel` before the adapter forces the active prompt
|
|
31
|
+
* loop to return "cancelled". `query.interrupt()` normally makes the SDK
|
|
32
|
+
* yield a trailing idle within milliseconds, and the loop returns through its
|
|
33
|
+
* usual path — so this timer is armed and cleared, never fired, on healthy
|
|
34
|
+
* cancels. It only trips when the SDK is genuinely wedged (e.g. a
|
|
35
|
+
* `TaskOutput { block: true }` poll against a hung background task — issue
|
|
36
|
+
* #680) and never yields. The value is deliberately loose: it's an
|
|
37
|
+
* "obviously stuck" ceiling, not a guess at interrupt latency, so it can't
|
|
38
|
+
* pre-empt a slow-but-healthy interrupt. */
|
|
39
|
+
const DEFAULT_FORCE_CANCEL_GRACE_MS = 30_000;
|
|
30
40
|
/** Compute a stable fingerprint of the session-defining params so we can
|
|
31
41
|
* detect when a loadSession/resumeSession call requires tearing down and
|
|
32
42
|
* recreating the underlying Query process. MCP servers are sorted by name
|
|
@@ -92,9 +102,39 @@ const LOCAL_ONLY_COMMANDS = new Set(["/context", "/heapdump", "/extra-usage"]);
|
|
|
92
102
|
// payload in these XML-like markers that the CLI uses for its own display.
|
|
93
103
|
// The live prompt loop drops them; replay must strip them too or they leak
|
|
94
104
|
// into the UI on session/load.
|
|
95
|
-
const
|
|
105
|
+
const LOCAL_COMMAND_MARKERS = [
|
|
106
|
+
"command-name",
|
|
107
|
+
"command-message",
|
|
108
|
+
"command-args",
|
|
109
|
+
"local-command-stdout",
|
|
110
|
+
"local-command-stderr",
|
|
111
|
+
].map((tag) => ({ open: `<${tag}>`, close: `</${tag}>` }));
|
|
112
|
+
// Single-pass scanner that removes each `<tag>…</tag>` marker (matching the
|
|
113
|
+
// nearest closing tag of the same name, like a lazy regex would).
|
|
96
114
|
function stripMarkerTags(text) {
|
|
97
|
-
|
|
115
|
+
const dead = new Set();
|
|
116
|
+
let result = "";
|
|
117
|
+
let copiedUpTo = 0;
|
|
118
|
+
let i = 0;
|
|
119
|
+
while (i < text.length) {
|
|
120
|
+
if (text[i] === "<") {
|
|
121
|
+
const marker = LOCAL_COMMAND_MARKERS.find((m) => !dead.has(m.open) && text.startsWith(m.open, i));
|
|
122
|
+
if (marker) {
|
|
123
|
+
const end = text.indexOf(marker.close, i + marker.open.length);
|
|
124
|
+
if (end !== -1) {
|
|
125
|
+
result += text.slice(copiedUpTo, i);
|
|
126
|
+
i = copiedUpTo = end + marker.close.length;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// No closing marker remains anywhere ahead, and `indexOf` only ever
|
|
130
|
+
// searches forward from here on, so stop treating this tag as an
|
|
131
|
+
// opener — that avoids rescanning the tail for it on every match.
|
|
132
|
+
dead.add(marker.open);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
i++;
|
|
136
|
+
}
|
|
137
|
+
return result + text.slice(copiedUpTo);
|
|
98
138
|
}
|
|
99
139
|
/**
|
|
100
140
|
* Return user-message content with local-command marker tags removed, or
|
|
@@ -204,6 +244,10 @@ export function describeAlwaysAllow(suggestions, toolName) {
|
|
|
204
244
|
export class ClaudeAcpAgent {
|
|
205
245
|
constructor(client, logger) {
|
|
206
246
|
this.backgroundTerminals = {};
|
|
247
|
+
/** Grace period before a `session/cancel` forces a wedged prompt loop to
|
|
248
|
+
* return "cancelled". See {@link DEFAULT_FORCE_CANCEL_GRACE_MS}. Mutable so
|
|
249
|
+
* tests can shrink it. */
|
|
250
|
+
this.forceCancelGraceMs = DEFAULT_FORCE_CANCEL_GRACE_MS;
|
|
207
251
|
this.sessions = {};
|
|
208
252
|
this.client = client;
|
|
209
253
|
this.toolUseCache = {};
|
|
@@ -433,6 +477,12 @@ export class ClaudeAcpAgent {
|
|
|
433
477
|
// forward it to clients as structured `data`, sparing them from
|
|
434
478
|
// pattern-matching on the human-readable message text.
|
|
435
479
|
let lastAssistantError;
|
|
480
|
+
// When a streaming classifier refuses a turn, the assistant message carries
|
|
481
|
+
// stop_reason "refusal" and structured stop_details. We capture the
|
|
482
|
+
// human-readable explanation here so the terminal `result` can surface it
|
|
483
|
+
// to the user (the refused assistant message itself usually has no content)
|
|
484
|
+
// and report ACP's dedicated `refusal` stop reason.
|
|
485
|
+
let lastRefusalExplanation = null;
|
|
436
486
|
// Tracks whether we're inside a compaction. The SDK emits the terminal
|
|
437
487
|
// `status` (compact_result success/failed) twice for a single failed
|
|
438
488
|
// compaction, and the two messages are indistinguishable — so we report the
|
|
@@ -465,9 +515,26 @@ export class ClaudeAcpAgent {
|
|
|
465
515
|
let handedOff = false;
|
|
466
516
|
let errored = false;
|
|
467
517
|
let stopReason = "end_turn";
|
|
518
|
+
// Wake-up channel so cancel() can force this loop to return "cancelled"
|
|
519
|
+
// even when query.next() is wedged and never yields again (issue #680).
|
|
520
|
+
const cancelController = new AbortController();
|
|
521
|
+
session.cancelController = cancelController;
|
|
522
|
+
const cancelled = new Promise((resolve) => {
|
|
523
|
+
cancelController.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
524
|
+
});
|
|
468
525
|
try {
|
|
469
526
|
while (true) {
|
|
470
|
-
const
|
|
527
|
+
const nextMessage = session.query.next();
|
|
528
|
+
const next = await Promise.race([nextMessage, cancelled]);
|
|
529
|
+
if (cancelController.signal.aborted) {
|
|
530
|
+
// The SDK never yielded after interrupt() (e.g. a wedged TaskOutput
|
|
531
|
+
// block). Abandon the in-flight next() — swallowing any later
|
|
532
|
+
// rejection so it can't surface as an unhandled rejection — and
|
|
533
|
+
// honor the cancel per the ACP contract.
|
|
534
|
+
void nextMessage.catch(() => { });
|
|
535
|
+
return { stopReason: "cancelled" };
|
|
536
|
+
}
|
|
537
|
+
const { value: message, done } = next;
|
|
471
538
|
if (done || !message) {
|
|
472
539
|
if (session.cancelled) {
|
|
473
540
|
return { stopReason: "cancelled" };
|
|
@@ -607,6 +674,22 @@ export class ClaudeAcpAgent {
|
|
|
607
674
|
});
|
|
608
675
|
break;
|
|
609
676
|
}
|
|
677
|
+
case "commands_changed": {
|
|
678
|
+
// Push the full slash-command list after a mid-session change
|
|
679
|
+
// (e.g. skills discovered dynamically as the agent works in a
|
|
680
|
+
// subdirectory). The client should REPLACE its cached command
|
|
681
|
+
// list with this payload: supportedCommands() is captured once
|
|
682
|
+
// at initialize and never reflects mid-session changes, so we
|
|
683
|
+
// forward message.commands directly rather than re-querying.
|
|
684
|
+
await this.client.sessionUpdate({
|
|
685
|
+
sessionId: message.session_id,
|
|
686
|
+
update: {
|
|
687
|
+
sessionUpdate: "available_commands_update",
|
|
688
|
+
availableCommands: getAvailableSlashCommands(message.commands),
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
610
693
|
case "hook_started":
|
|
611
694
|
case "hook_progress":
|
|
612
695
|
case "hook_response":
|
|
@@ -674,6 +757,25 @@ export class ClaudeAcpAgent {
|
|
|
674
757
|
}
|
|
675
758
|
break;
|
|
676
759
|
}
|
|
760
|
+
// A refusal can arrive on any result subtype (and may even set
|
|
761
|
+
// is_error), so handle it before the subtype switch — otherwise the
|
|
762
|
+
// is_error throw below would surface it as an internal error. The
|
|
763
|
+
// refused assistant message carries no visible content, so surface
|
|
764
|
+
// the classifier's explanation (when available) and report ACP's
|
|
765
|
+
// dedicated `refusal` stop reason.
|
|
766
|
+
if (message.stop_reason === "refusal" && !isTaskNotification) {
|
|
767
|
+
if (lastRefusalExplanation) {
|
|
768
|
+
await this.client.sessionUpdate({
|
|
769
|
+
sessionId: params.sessionId,
|
|
770
|
+
update: {
|
|
771
|
+
sessionUpdate: "agent_message_chunk",
|
|
772
|
+
content: { type: "text", text: lastRefusalExplanation },
|
|
773
|
+
},
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
stopReason = "refusal";
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
677
779
|
switch (message.subtype) {
|
|
678
780
|
case "success": {
|
|
679
781
|
if (message.result.includes("Please run /login")) {
|
|
@@ -824,6 +926,9 @@ export class ClaudeAcpAgent {
|
|
|
824
926
|
if (message.error) {
|
|
825
927
|
lastAssistantError = message.error;
|
|
826
928
|
}
|
|
929
|
+
if (message.message.stop_reason === "refusal") {
|
|
930
|
+
lastRefusalExplanation = message.message.stop_details?.explanation ?? null;
|
|
931
|
+
}
|
|
827
932
|
}
|
|
828
933
|
// Strip <command-*>/<local-command-stdout> markers and render any
|
|
829
934
|
// remaining prose. Skill bodies and built-in slash commands (e.g.
|
|
@@ -943,6 +1048,16 @@ export class ClaudeAcpAgent {
|
|
|
943
1048
|
throw error;
|
|
944
1049
|
}
|
|
945
1050
|
finally {
|
|
1051
|
+
// The loop is returning — interrupt() succeeded or the prompt finished
|
|
1052
|
+
// — so disarm the force-cancel backstop and release the wake-up channel
|
|
1053
|
+
// (only if we still own it; a handoff installs the next prompt's).
|
|
1054
|
+
if (session.forceCancelTimer) {
|
|
1055
|
+
clearTimeout(session.forceCancelTimer);
|
|
1056
|
+
session.forceCancelTimer = undefined;
|
|
1057
|
+
}
|
|
1058
|
+
if (session.cancelController === cancelController) {
|
|
1059
|
+
session.cancelController = undefined;
|
|
1060
|
+
}
|
|
946
1061
|
if (!handedOff) {
|
|
947
1062
|
session.promptRunning = false;
|
|
948
1063
|
if (errored) {
|
|
@@ -978,6 +1093,27 @@ export class ClaudeAcpAgent {
|
|
|
978
1093
|
pending.resolve(true);
|
|
979
1094
|
}
|
|
980
1095
|
session.pendingMessages.clear();
|
|
1096
|
+
// Arm a backstop before interrupting: if a prompt is actively consuming
|
|
1097
|
+
// the query and interrupt() doesn't make the SDK yield (e.g. a wedged
|
|
1098
|
+
// TaskOutput block — issue #680), force the loop to return "cancelled"
|
|
1099
|
+
// after the floor elapses so the pending session/prompt still resolves per
|
|
1100
|
+
// the ACP cancellation contract instead of hanging forever. The loop's
|
|
1101
|
+
// `finally` clears this timer when interrupt() works and it returns through
|
|
1102
|
+
// the normal idle path, so on healthy cancels it is armed but never fires.
|
|
1103
|
+
//
|
|
1104
|
+
// Arm at most once per turn: the floor is an absolute ceiling from the
|
|
1105
|
+
// first cancel, so a client that re-sends cancel (each call still retries
|
|
1106
|
+
// interrupt() below) can't keep pushing the deadline out.
|
|
1107
|
+
if (session.promptRunning &&
|
|
1108
|
+
session.cancelController &&
|
|
1109
|
+
!session.cancelController.signal.aborted &&
|
|
1110
|
+
!session.forceCancelTimer) {
|
|
1111
|
+
const cancelController = session.cancelController;
|
|
1112
|
+
session.forceCancelTimer = setTimeout(() => {
|
|
1113
|
+
this.logger.error(`Session ${params.sessionId}: cancel floor elapsed without the SDK yielding; forcing "cancelled". The underlying query may still be wedged — a new session may be required.`);
|
|
1114
|
+
cancelController.abort();
|
|
1115
|
+
}, this.forceCancelGraceMs);
|
|
1116
|
+
}
|
|
981
1117
|
await session.query.interrupt();
|
|
982
1118
|
}
|
|
983
1119
|
/** Cleanly tear down a session: cancel in-flight work, dispose resources,
|
|
@@ -988,6 +1124,18 @@ export class ClaudeAcpAgent {
|
|
|
988
1124
|
return;
|
|
989
1125
|
}
|
|
990
1126
|
await this.cancel({ sessionId });
|
|
1127
|
+
// cancel() arms the force-cancel floor and interrupts gracefully, but a
|
|
1128
|
+
// wedged prompt loop only wakes when `cancelController` aborts — closing
|
|
1129
|
+
// the query/abortController below doesn't touch it. Since we're tearing the
|
|
1130
|
+
// session down anyway, wake the loop now so the in-flight prompt() resolves
|
|
1131
|
+
// immediately instead of after the floor, and clear the timer so it can't
|
|
1132
|
+
// outlive the deleted session (it isn't unref'd and would otherwise keep
|
|
1133
|
+
// the event loop alive until it fires).
|
|
1134
|
+
if (session.forceCancelTimer) {
|
|
1135
|
+
clearTimeout(session.forceCancelTimer);
|
|
1136
|
+
session.forceCancelTimer = undefined;
|
|
1137
|
+
}
|
|
1138
|
+
session.cancelController?.abort();
|
|
991
1139
|
session.settingsManager.dispose();
|
|
992
1140
|
session.abortController.abort();
|
|
993
1141
|
session.query.close();
|
|
@@ -1013,19 +1161,6 @@ export class ClaudeAcpAgent {
|
|
|
1013
1161
|
await deleteSession(params.sessionId);
|
|
1014
1162
|
return {};
|
|
1015
1163
|
}
|
|
1016
|
-
async unstable_setSessionModel(params) {
|
|
1017
|
-
const session = this.sessions[params.sessionId];
|
|
1018
|
-
if (!session) {
|
|
1019
|
-
throw new Error("Session not found");
|
|
1020
|
-
}
|
|
1021
|
-
// Resolve aliases (e.g. "opus", "opus[1m]") to canonical model IDs so
|
|
1022
|
-
// downstream lookups in modelInfos succeed and the effort option isn't
|
|
1023
|
-
// silently dropped.
|
|
1024
|
-
const resolved = resolveModelPreference(session.modelInfos, params.modelId);
|
|
1025
|
-
const modelId = resolved?.value ?? params.modelId;
|
|
1026
|
-
await session.query.setModel(modelId);
|
|
1027
|
-
await this.updateConfigOption(params.sessionId, "model", modelId);
|
|
1028
|
-
}
|
|
1029
1164
|
async setSessionMode(params) {
|
|
1030
1165
|
if (!this.sessions[params.sessionId]) {
|
|
1031
1166
|
throw new Error("Session not found");
|
|
@@ -1408,7 +1543,6 @@ export class ClaudeAcpAgent {
|
|
|
1408
1543
|
return {
|
|
1409
1544
|
sessionId: params.sessionId,
|
|
1410
1545
|
modes: existingSession.modes,
|
|
1411
|
-
models: existingSession.models,
|
|
1412
1546
|
configOptions: existingSession.configOptions,
|
|
1413
1547
|
};
|
|
1414
1548
|
}
|
|
@@ -1428,7 +1562,6 @@ export class ClaudeAcpAgent {
|
|
|
1428
1562
|
return {
|
|
1429
1563
|
sessionId: response.sessionId,
|
|
1430
1564
|
modes: response.modes,
|
|
1431
|
-
models: response.models,
|
|
1432
1565
|
configOptions: response.configOptions,
|
|
1433
1566
|
};
|
|
1434
1567
|
}
|
|
@@ -1740,7 +1873,6 @@ export class ClaudeAcpAgent {
|
|
|
1740
1873
|
};
|
|
1741
1874
|
return {
|
|
1742
1875
|
sessionId,
|
|
1743
|
-
models,
|
|
1744
1876
|
modes,
|
|
1745
1877
|
configOptions,
|
|
1746
1878
|
};
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.41.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,22 +60,22 @@
|
|
|
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.24.0",
|
|
64
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.162",
|
|
65
65
|
"zod": "^3.25.0 || ^4.0.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@anthropic-ai/sdk": "0.100.
|
|
68
|
+
"@anthropic-ai/sdk": "0.100.1",
|
|
69
69
|
"@eslint/js": "10.0.1",
|
|
70
70
|
"@types/node": "25.9.1",
|
|
71
|
-
"@typescript-eslint/eslint-plugin": "8.60.
|
|
72
|
-
"@typescript-eslint/parser": "8.60.
|
|
73
|
-
"eslint": "10.4.
|
|
71
|
+
"@typescript-eslint/eslint-plugin": "8.60.1",
|
|
72
|
+
"@typescript-eslint/parser": "8.60.1",
|
|
73
|
+
"eslint": "10.4.1",
|
|
74
74
|
"eslint-config-prettier": "10.1.8",
|
|
75
75
|
"globals": "17.6.0",
|
|
76
76
|
"prettier": "3.8.3",
|
|
77
77
|
"ts-node": "10.9.2",
|
|
78
78
|
"typescript": "6.0.3",
|
|
79
|
-
"vitest": "4.1.
|
|
79
|
+
"vitest": "4.1.8"
|
|
80
80
|
}
|
|
81
81
|
}
|