claude-code-acp-ts 0.38.2 → 0.52.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.
@@ -0,0 +1,82 @@
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
+ * Each question is followed by its own optional free-text "custom answer" field
57
+ * (`question_<n>_custom`), mirroring the CLI's per-question "Other" box: the
58
+ * user can type their own answer instead of picking an option, scoped to that
59
+ * specific question. Nothing is marked required, so the user can also just skip
60
+ * — matching the built-in tool, which always offers Skip + a free-text box.
61
+ */
62
+ export declare function askUserQuestionsToCreateRequest(questions: AskUserQuestion[], sessionId: string, toolCallId: string | undefined): CreateElicitationRequest;
63
+ /** Outcome of an AskUserQuestion elicitation, decoupled from any transport. */
64
+ export type AskUserQuestionOutcome = {
65
+ action: "answered";
66
+ updatedInput: Record<string, unknown>;
67
+ } | {
68
+ action: "cancel";
69
+ };
70
+ /**
71
+ * Fold an ACP elicitation response into the AskUserQuestion tool's input.
72
+ *
73
+ * Selected labels are read back from the indexed form fields and written into
74
+ * `answers` as a `{ [questionText]: label }` map (comma-joining multi-selects)
75
+ * — the key shape the tool's own `call()` reads. A non-empty per-question
76
+ * custom-answer field (`question_<n>_custom`) takes precedence over that
77
+ * question's selection, since the user typed their own answer instead of
78
+ * picking one. Decline yields empty answers (the model is told the user skipped
79
+ * rather than the turn aborting); cancel aborts the tool call.
80
+ */
81
+ export declare function applyAskElicitationResponse(response: CreateElicitationResponse, toolInput: Record<string, unknown>, questions: AskUserQuestion[]): AskUserQuestionOutcome;
82
+ //# 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;AA4CD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,eAAe,EAAE,EAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,wBAAwB,CAwD1B;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;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,yBAAyB,EACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,SAAS,EAAE,eAAe,EAAE,GAC3B,sBAAsB,CAkCxB"}
@@ -0,0 +1,211 @@
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
+ /**
66
+ * Form-field key for the per-question free-text "custom answer" field that sits
67
+ * alongside `question_<n>`. Mirrors the first-party clients, where every
68
+ * question carries its own "Other" box rather than one form-level field.
69
+ */
70
+ function questionCustomFieldKey(index) {
71
+ return `question_${index}_custom`;
72
+ }
73
+ /**
74
+ * `_meta` key under which a bridged enum option carries its structured
75
+ * `description`/`preview`, since ACP's `EnumOption` has no field for either.
76
+ * Namespaced like the agent's other `_meta` extensions (`_claude/...`).
77
+ */
78
+ const OPTION_META_KEY = "_claude/askUserQuestionOption";
79
+ /**
80
+ * Build the `_meta` payload that carries an option's structured `description`
81
+ * and optional `preview` for clients that can render them as distinct fields.
82
+ * Returns `undefined` when neither is present, so we don't emit an empty `_meta`.
83
+ */
84
+ function askUserQuestionOptionMeta(option) {
85
+ const detail = {};
86
+ if (option.description) {
87
+ detail.description = option.description;
88
+ }
89
+ if (option.preview) {
90
+ detail.preview = option.preview;
91
+ }
92
+ return Object.keys(detail).length > 0 ? { [OPTION_META_KEY]: detail } : undefined;
93
+ }
94
+ /**
95
+ * Render the AskUserQuestion tool's questions as an ACP form elicitation.
96
+ *
97
+ * Fields are keyed by a short stable id (`question_<n>`) rather than the full
98
+ * question text, so the question text appears in exactly one place per field.
99
+ * Single-select questions use a titled `oneOf` enum; multi-select questions use
100
+ * an array with a titled `anyOf` item enum. The enum `const` is always the
101
+ * option label, since that is what the tool records as the answer.
102
+ *
103
+ * Each question is followed by its own optional free-text "custom answer" field
104
+ * (`question_<n>_custom`), mirroring the CLI's per-question "Other" box: the
105
+ * user can type their own answer instead of picking an option, scoped to that
106
+ * specific question. Nothing is marked required, so the user can also just skip
107
+ * — matching the built-in tool, which always offers Skip + a free-text box.
108
+ */
109
+ export function askUserQuestionsToCreateRequest(questions, sessionId, toolCallId) {
110
+ const single = questions.length === 1;
111
+ const properties = {};
112
+ questions.forEach((question, index) => {
113
+ const options = question.options.map((option) => {
114
+ const enumOption = {
115
+ const: option.label,
116
+ // `title` stays a flattened "label — description" string so clients that
117
+ // only read `const`/`title` still surface the description. Clients that
118
+ // understand the `_meta` below should prefer its structured fields (and
119
+ // treat `const` as the clean label) rather than parsing this title.
120
+ title: option.description ? `${option.label} — ${option.description}` : option.label,
121
+ };
122
+ // The SDK option carries a `description` (secondary text) and an optional
123
+ // `preview` (mockups, code snippets, comparisons shown on focus). Neither
124
+ // has a structural slot in `EnumOption`, so forward them under ACP's
125
+ // reserved `_meta` extension point for clients that can render them.
126
+ const meta = askUserQuestionOptionMeta(option);
127
+ if (meta) {
128
+ enumOption._meta = meta;
129
+ }
130
+ return enumOption;
131
+ });
132
+ // For a single question the prompt is carried by `message`, so we don't
133
+ // repeat it in the field description. With multiple questions each field
134
+ // needs its own question text.
135
+ const description = single ? undefined : question.question;
136
+ const title = question.header || undefined;
137
+ properties[questionFieldKey(index)] = question.multiSelect
138
+ ? { type: "array", title, description, items: { anyOf: options } }
139
+ : { type: "string", title, description, oneOf: options };
140
+ properties[questionCustomFieldKey(index)] = {
141
+ type: "string",
142
+ title: "Other",
143
+ description: "Type your own answer instead of choosing an option above (optional).",
144
+ };
145
+ });
146
+ const requestedSchema = {
147
+ type: "object",
148
+ properties,
149
+ };
150
+ const message = single ? questions[0].question : "Please answer the following questions.";
151
+ return {
152
+ mode: "form",
153
+ sessionId,
154
+ ...(toolCallId ? { toolCallId } : {}),
155
+ message,
156
+ requestedSchema,
157
+ };
158
+ }
159
+ /**
160
+ * Fold an ACP elicitation response into the AskUserQuestion tool's input.
161
+ *
162
+ * Selected labels are read back from the indexed form fields and written into
163
+ * `answers` as a `{ [questionText]: label }` map (comma-joining multi-selects)
164
+ * — the key shape the tool's own `call()` reads. A non-empty per-question
165
+ * custom-answer field (`question_<n>_custom`) takes precedence over that
166
+ * question's selection, since the user typed their own answer instead of
167
+ * picking one. Decline yields empty answers (the model is told the user skipped
168
+ * rather than the turn aborting); cancel aborts the tool call.
169
+ */
170
+ export function applyAskElicitationResponse(response, toolInput, questions) {
171
+ if (response.action === "cancel") {
172
+ return { action: "cancel" };
173
+ }
174
+ if (response.action === "decline") {
175
+ return { action: "answered", updatedInput: { ...toolInput, answers: {} } };
176
+ }
177
+ const content = response.content ?? {};
178
+ // Typed against the tool's own output schema so the answer/response shapes
179
+ // stay in sync with what the built-in tool's call() expects to read back.
180
+ const answers = {};
181
+ questions.forEach((question, index) => {
182
+ // A typed custom answer wins over the selection: the user chose to write
183
+ // their own answer for this question instead of picking an option.
184
+ const custom = content[questionCustomFieldKey(index)];
185
+ if (typeof custom === "string" && custom.trim() !== "") {
186
+ answers[question.question] = custom.trim();
187
+ return;
188
+ }
189
+ const value = content[questionFieldKey(index)];
190
+ if (value === undefined || value === null) {
191
+ return;
192
+ }
193
+ const text = Array.isArray(value) ? value.join(", ") : String(value);
194
+ if (text === "") {
195
+ return;
196
+ }
197
+ answers[question.question] = text;
198
+ });
199
+ return { action: "answered", updatedInput: { ...toolInput, answers } };
200
+ }
201
+ /**
202
+ * Coerce an arbitrary MCP `requestedSchema` into an ACP `ElicitationSchema`.
203
+ * The two are structurally compatible JSON Schemas; we just guarantee the
204
+ * `type: "object"` discriminator is present.
205
+ */
206
+ function normalizeElicitationSchema(schema) {
207
+ if (!schema || typeof schema !== "object") {
208
+ return { type: "object", properties: {} };
209
+ }
210
+ return { ...schema, type: "object" };
211
+ }
package/dist/index.js CHANGED
@@ -2,10 +2,9 @@
2
2
  import { resolveSettings } from "@anthropic-ai/claude-agent-sdk";
3
3
  import { claudeCliPath, runAcp } from "./acp-agent.js";
4
4
  import packageJson from "../package.json" with { type: "json" };
5
- if (process.argv.includes("-v") || process.argv.includes("--version")) {
6
- console.log(packageJson.version);
7
- process.exit(0);
8
- }
5
+ // `--cli` is checked first so that `--version`/`-v` (and any other flags) are
6
+ // forwarded to the wrapped native CLI rather than swallowed by our own version
7
+ // handler below. Our version flag only applies when not delegating.
9
8
  if (process.argv.includes("--cli")) {
10
9
  const { spawn } = await import("node:child_process");
11
10
  const args = process.argv.slice(2).filter((arg) => arg !== "--cli");
@@ -36,6 +35,10 @@ if (process.argv.includes("--cli")) {
36
35
  process.exit(1);
37
36
  });
38
37
  }
38
+ else if (process.argv.includes("--version") || process.argv.includes("-v")) {
39
+ console.log(packageJson.version);
40
+ process.exit(0);
41
+ }
39
42
  else {
40
43
  // Apply env vars from the managed-policy tier before any SDK call so the
41
44
  // SDK subprocess inherits them. Going through resolveSettings (vs. a raw
package/dist/settings.js CHANGED
@@ -35,13 +35,16 @@ function getManagedSettingsPath() {
35
35
  * `filterEscalatingDefaultMode`, matching the CLI's trust policy.
36
36
  */
37
37
  export class SettingsManager {
38
+ cwd;
39
+ effective = {};
40
+ watchers = [];
41
+ onChange;
42
+ logger;
43
+ initialized = false;
44
+ disposed = false;
45
+ debounceTimer = null;
46
+ initPromise = null;
38
47
  constructor(cwd, options) {
39
- this.effective = {};
40
- this.watchers = [];
41
- this.initialized = false;
42
- this.disposed = false;
43
- this.debounceTimer = null;
44
- this.initPromise = null;
45
48
  this.cwd = cwd;
46
49
  this.onChange = options?.onChange;
47
50
  this.logger = options?.logger ?? console;
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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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;
@@ -309,7 +325,8 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
309
325
  if ("is_error" in toolResult &&
310
326
  toolResult.is_error &&
311
327
  toolResult.content &&
312
- toolResult.content.length > 0) {
328
+ toolResult.content.length > 0 &&
329
+ !(toolUse?.name === "Bash" && supportsTerminalOutput)) {
313
330
  // Only return errors
314
331
  return toAcpContentUpdate(toolResult.content, true);
315
332
  }
@@ -349,7 +366,9 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
349
366
  // Extract output and exit code from either format:
350
367
  // 1. BetaBashCodeExecutionResultBlock: { type: "bash_code_execution_result", stdout, stderr, return_code }
351
368
  // 2. Plain string content from a regular tool_result
352
- // 3. Array content (e.g. [{ type: "text", text: "..." }])
369
+ // 3. Array content (e.g. [{ type: "text", text: "..." }] for stdout,
370
+ // or [{ type: "image", source: {...} }] when the local Bash tool
371
+ // produces an image, e.g. piping a base64 data URI)
353
372
  let output = "";
354
373
  let exitCode = isError ? 1 : 0;
355
374
  if (result &&
@@ -363,11 +382,19 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
363
382
  else if (typeof result === "string") {
364
383
  output = result;
365
384
  }
366
- else if (Array.isArray(result) &&
367
- result.length > 0 &&
368
- "text" in result[0] &&
369
- typeof result[0].text === "string") {
370
- output = result.map((c) => c.text).join("\n");
385
+ else if (Array.isArray(result) && result.length > 0) {
386
+ const textOnly = result.every((c) => c && typeof c === "object" && typeof c.text === "string");
387
+ if (textOnly) {
388
+ output = result.map((c) => c.text).join("\n");
389
+ }
390
+ else {
391
+ // Image (or mixed non-text) content. Binary payloads can't be
392
+ // streamed through the terminal-output _meta channel, so bypass
393
+ // it and surface the blocks as ACP content. This handles the
394
+ // local Bash tool's image output, which previously failed the
395
+ // text-only guard and was silently dropped.
396
+ return toAcpContentUpdate(result, isError);
397
+ }
371
398
  }
372
399
  if (supportsTerminalOutput) {
373
400
  return {
package/dist/utils.js CHANGED
@@ -2,11 +2,9 @@
2
2
  import { WritableStream, ReadableStream } from "node:stream/web";
3
3
  // Useful for bridging push-based and async-iterator-based code.
4
4
  export class Pushable {
5
- constructor() {
6
- this.queue = [];
7
- this.resolvers = [];
8
- this.done = false;
9
- }
5
+ queue = [];
6
+ resolvers = [];
7
+ done = false;
10
8
  push(item) {
11
9
  if (this.resolvers.length > 0) {
12
10
  const resolve = this.resolvers.shift();
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.38.2",
6
+ "version": "0.52.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",
@@ -49,33 +49,37 @@
49
49
  "sdk",
50
50
  "code"
51
51
  ],
52
- "homepage": "https://github.com/dongdada29/claude-code-acp-ts#readme",
52
+ "homepage": "https://github.com/agentclientprotocol/claude-agent-acp#readme",
53
53
  "bugs": {
54
- "url": "https://github.com/dongdada29/claude-code-acp-ts/issues"
54
+ "url": "https://github.com/agentclientprotocol/claude-agent-acp/issues"
55
55
  },
56
56
  "repository": {
57
57
  "type": "git",
58
- "url": "git+https://github.com/dongdada29/claude-code-acp-ts.git"
58
+ "url": "git+https://github.com/agentclientprotocol/claude-agent-acp.git"
59
59
  },
60
60
  "author": "Zed Industries",
61
61
  "license": "Apache-2.0",
62
+ "engines": {
63
+ "node": ">=22"
64
+ },
62
65
  "dependencies": {
63
- "@agentclientprotocol/sdk": "0.22.1",
64
- "@anthropic-ai/claude-agent-sdk": "0.3.146",
66
+ "@agentclientprotocol/sdk": "1.0.0",
67
+ "@anthropic-ai/claude-agent-sdk": "0.3.191",
65
68
  "zod": "^3.25.0 || ^4.0.0"
66
69
  },
67
70
  "devDependencies": {
68
- "@anthropic-ai/sdk": "0.97.1",
71
+ "@anthropic-ai/sdk": "0.106.0",
69
72
  "@eslint/js": "10.0.1",
70
- "@types/node": "25.9.1",
71
- "@typescript-eslint/eslint-plugin": "8.59.4",
72
- "@typescript-eslint/parser": "8.59.4",
73
- "eslint": "10.4.0",
73
+ "@tsconfig/node22": "22.0.5",
74
+ "@types/node": "26.0.1",
75
+ "@typescript-eslint/eslint-plugin": "8.62.0",
76
+ "@typescript-eslint/parser": "8.62.0",
77
+ "eslint": "10.5.0",
74
78
  "eslint-config-prettier": "10.1.8",
75
- "globals": "17.6.0",
76
- "prettier": "3.8.3",
79
+ "globals": "17.7.0",
80
+ "prettier": "3.8.4",
77
81
  "ts-node": "10.9.2",
78
82
  "typescript": "6.0.3",
79
- "vitest": "4.1.7"
83
+ "vitest": "4.1.9"
80
84
  }
81
85
  }