@agentclientprotocol/claude-agent-acp 0.58.1 → 0.60.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/tools.d.ts CHANGED
@@ -3,7 +3,6 @@ import { HookCallback } from "@anthropic-ai/claude-agent-sdk";
3
3
  import { TaskCreateInput, TaskCreateOutput, TaskUpdateInput } from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
4
4
  import { ToolResultBlockParam, WebSearchToolResultBlockParam } from "@anthropic-ai/sdk/resources";
5
5
  import { BetaBashCodeExecutionToolResultBlockParam, BetaCodeExecutionToolResultBlockParam, BetaRequestMCPToolResultBlockParam, BetaTextEditorCodeExecutionToolResultBlockParam, BetaToolResultBlockParam, BetaToolSearchToolResultBlockParam, BetaWebFetchToolResultBlockParam, BetaWebSearchToolResultBlockParam } from "@anthropic-ai/sdk/resources/beta.mjs";
6
- import { Logger } from "./acp-agent.js";
7
6
  interface ToolInfo {
8
7
  title: string;
9
8
  kind: ToolKind;
@@ -35,7 +34,7 @@ interface ToolUpdate {
35
34
  */
36
35
  export declare function toDisplayPath(filePath: string, cwd?: string): string;
37
36
  export declare function toolInfoFromToolUse(toolUse: any, supportsTerminalOutput?: boolean, cwd?: string): ToolInfo;
38
- export declare function toolUpdateFromToolResult(toolResult: ToolResultBlockParam | BetaToolResultBlockParam | BetaWebSearchToolResultBlockParam | BetaWebFetchToolResultBlockParam | WebSearchToolResultBlockParam | BetaCodeExecutionToolResultBlockParam | BetaBashCodeExecutionToolResultBlockParam | BetaTextEditorCodeExecutionToolResultBlockParam | BetaRequestMCPToolResultBlockParam | BetaToolSearchToolResultBlockParam, toolUse: any | undefined, supportsTerminalOutput?: boolean): ToolUpdate;
37
+ export declare function toolUpdateFromToolResult(toolResult: ToolResultBlockParam | BetaToolResultBlockParam | BetaWebSearchToolResultBlockParam | BetaWebFetchToolResultBlockParam | WebSearchToolResultBlockParam | BetaCodeExecutionToolResultBlockParam | BetaBashCodeExecutionToolResultBlockParam | BetaTextEditorCodeExecutionToolResultBlockParam | BetaRequestMCPToolResultBlockParam | BetaToolSearchToolResultBlockParam, toolUse: any | undefined, supportsTerminalOutput?: boolean, toolUseResult?: unknown): ToolUpdate;
39
38
  export type ClaudePlanEntry = {
40
39
  content: string;
41
40
  status: "pending" | "in_progress" | "completed";
@@ -82,7 +81,7 @@ export declare function toolUpdateFromDiffToolResponse(toolResponse: unknown): {
82
81
  export declare const registerHookCallback: (toolUseID: string, { onPostToolUseHook, }: {
83
82
  onPostToolUseHook?: (toolUseID: string, toolInput: unknown, toolResponse: unknown) => Promise<void>;
84
83
  }) => void;
85
- export declare const createPostToolUseHook: (logger?: Logger, options?: {
84
+ export declare const createPostToolUseHook: (options?: {
86
85
  onEnterPlanMode?: () => Promise<void>;
87
86
  }) => HookCallback;
88
87
  /**
@@ -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
@@ -338,7 +338,80 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
338
338
  };
339
339
  }
340
340
  }
341
- export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOutput = false) {
341
+ /**
342
+ * Narrow the untyped message-level `tool_use_result` toward a per-tool Output
343
+ * shape: rejects everything but a plain non-null object (arrays pass a bare
344
+ * `typeof === "object"` check, so they're excluded here). The returned value
345
+ * is only *nominally* typed — it arrives over the wire from arbitrary CLI
346
+ * versions, so each caller must still guard the specific fields it reads
347
+ * before trusting them.
348
+ */
349
+ function structuredResult(toolUseResult) {
350
+ return toolUseResult !== null &&
351
+ typeof toolUseResult === "object" &&
352
+ !Array.isArray(toolUseResult)
353
+ ? toolUseResult
354
+ : undefined;
355
+ }
356
+ /**
357
+ * Strip the model-directed trailer from a raw Agent/Task tool_result text:
358
+ * a `<usage>…</usage>` totals block and/or an
359
+ * `agentId: <id> (use SendMessage …)` continuation line at the end of the
360
+ * text. Both patterns are tail-anchored and independent (older CLIs emit
361
+ * variants with only one of them), so a format change makes them stop
362
+ * matching rather than mangle the report.
363
+ */
364
+ function stripAgentTrailer(text) {
365
+ return stripAgentIdLine(stripUsageBlock(text));
366
+ }
367
+ const USAGE_OPEN = "<usage>";
368
+ const USAGE_CLOSE = "</usage>";
369
+ /** Remove a trailing `<usage>…</usage>` block, plus trailing whitespace and
370
+ * one preceding newline. Matches from the *last* `<usage>` so a report that
371
+ * merely mentions the marker earlier isn't truncated at the mention. */
372
+ function stripUsageBlock(text) {
373
+ const body = text.trimEnd();
374
+ if (!body.endsWith(USAGE_CLOSE)) {
375
+ return text;
376
+ }
377
+ const open = body.lastIndexOf(USAGE_OPEN, body.length - USAGE_CLOSE.length - USAGE_OPEN.length);
378
+ if (open === -1) {
379
+ return text;
380
+ }
381
+ return body.slice(0, open > 0 && body[open - 1] === "\n" ? open - 1 : open);
382
+ }
383
+ /** The continuation line, anchored to a whole line so the regex has a single
384
+ * start position and no ambiguous repetition (`[\w-]+` can't consume the
385
+ * following space, `[^)]*` can't consume the closing paren) — it runs in
386
+ * linear time on any input. */
387
+ const AGENT_ID_LINE = /^agentId: [\w-]+ \([^)]*\)$/;
388
+ /** Remove a final `agentId: <id> (…)` line, plus trailing whitespace and the
389
+ * newline that preceded the line. */
390
+ function stripAgentIdLine(text) {
391
+ const body = text.trimEnd();
392
+ const lineStart = body.lastIndexOf("\n") + 1;
393
+ if (!AGENT_ID_LINE.test(body.slice(lineStart))) {
394
+ return text;
395
+ }
396
+ return body.slice(0, Math.max(lineStart - 1, 0));
397
+ }
398
+ /** Apply {@link stripAgentTrailer} across a raw tool_result `content` (plain
399
+ * string or block array), leaving non-text blocks untouched. */
400
+ function stripAgentTrailerFromContent(content) {
401
+ if (typeof content === "string") {
402
+ return stripAgentTrailer(content);
403
+ }
404
+ if (Array.isArray(content)) {
405
+ return content.map((block) => block !== null &&
406
+ typeof block === "object" &&
407
+ block.type === "text" &&
408
+ typeof block.text === "string"
409
+ ? { ...block, text: stripAgentTrailer(block.text) }
410
+ : block);
411
+ }
412
+ return content;
413
+ }
414
+ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOutput = false, toolUseResult) {
342
415
  if ("is_error" in toolResult &&
343
416
  toolResult.is_error &&
344
417
  toolResult.content &&
@@ -347,8 +420,57 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
347
420
  // Only return errors
348
421
  return toAcpContentUpdate(toolResult.content, true);
349
422
  }
423
+ // Shared raw-text fallback: renders the tool_result content the model saw.
424
+ // The structured cases below fall back to this when `tool_use_result` is
425
+ // absent or fails its shape guard (older CLIs, replayed sessions).
426
+ const rawContentUpdate = () => toAcpContentUpdate(toolResult.content, "is_error" in toolResult ? toolResult.is_error : false);
350
427
  switch (toolUse?.name) {
351
- case "Read":
428
+ case "Read": {
429
+ // The raw tool_result text is the model-facing view: line-numbered
430
+ // content plus any appended <system-reminder> blocks (malicious-code
431
+ // checks, memory staleness notes, …) that clients shouldn't see. The
432
+ // structured FileReadOutput carries the clean content — rebuild the
433
+ // line-numbered view from it. Non-text variants (image/notebook/pdf)
434
+ // fall back to the raw content blocks, which already render fine.
435
+ const structuredRead = structuredResult(toolUseResult);
436
+ if (structuredRead?.type === "text" &&
437
+ typeof structuredRead.file?.content === "string" &&
438
+ // An empty file has nothing to line-number; keep the raw view (the
439
+ // model-facing "file is empty" note) rather than a phantom blank line.
440
+ structuredRead.file.content.length > 0) {
441
+ // startLine is typed non-optional but defended anyway; a Read's
442
+ // `offset` input is the same 1-based starting line, so it beats a
443
+ // blind 1 when an emitter omits the field.
444
+ const startLine = structuredRead.file.startLine ??
445
+ toolUse.input?.offset ??
446
+ 1;
447
+ // A trailing newline is a line terminator, not an extra line — don't
448
+ // number a phantom empty line after it.
449
+ let numbered = structuredRead.file.content
450
+ .replace(/\n$/, "")
451
+ .split("\n")
452
+ .map((line, i) => `${startLine + i}\t${line}`)
453
+ .join("\n");
454
+ // The model-facing truncation banner doesn't survive reconstruction
455
+ // from file.content (the SDK flag exists for exactly this case) —
456
+ // re-establish it so a partial first page doesn't read as the whole
457
+ // file.
458
+ if (structuredRead.file.truncatedByTokenCap) {
459
+ const { numLines, totalLines } = structuredRead.file;
460
+ const detail = typeof numLines === "number" && typeof totalLines === "number"
461
+ ? `: showing ${numLines} of ${totalLines} lines`
462
+ : "";
463
+ numbered += `\n[File truncated${detail}]`;
464
+ }
465
+ return {
466
+ content: [
467
+ {
468
+ type: "content",
469
+ content: { type: "text", text: markdownEscape(numbered) },
470
+ },
471
+ ],
472
+ };
473
+ }
352
474
  if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
353
475
  return {
354
476
  content: toolResult.content.map((content) => ({
@@ -376,19 +498,60 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
376
498
  };
377
499
  }
378
500
  return {};
501
+ }
379
502
  case "Bash": {
380
503
  const result = toolResult.content;
381
504
  const terminalId = "tool_use_id" in toolResult ? String(toolResult.tool_use_id) : "";
382
505
  const isError = "is_error" in toolResult && toolResult.is_error;
383
506
  // Extract output and exit code from either format:
384
- // 1. BetaBashCodeExecutionResultBlock: { type: "bash_code_execution_result", stdout, stderr, return_code }
385
- // 2. Plain string content from a regular tool_result
386
- // 3. Array content (e.g. [{ type: "text", text: "..." }] for stdout,
507
+ // 1. The structured BashOutput (message-level tool_use_result): its
508
+ // stdout/stderr exclude the model-directed suffixes the raw text
509
+ // carries (stale-read hints, gh rate-limit hints, the
510
+ // persisted-output wrapper for too-large outputs — the interruption
511
+ // and truncation facts those carried are re-established from the
512
+ // structured flags below). Skipped for image output (the raw content
513
+ // array carries the actual image blocks) and backgrounded commands
514
+ // (the raw text carries the background-task notice; structured
515
+ // stdout may be empty).
516
+ // 2. BetaBashCodeExecutionResultBlock: { type: "bash_code_execution_result", stdout, stderr, return_code }
517
+ // 3. Plain string content from a regular tool_result
518
+ // 4. Array content (e.g. [{ type: "text", text: "..." }] for stdout,
387
519
  // or [{ type: "image", source: {...} }] when the local Bash tool
388
520
  // produces an image, e.g. piping a base64 data URI)
389
521
  let output = "";
390
522
  let exitCode = isError ? 1 : 0;
391
- if (result &&
523
+ const structuredBash = structuredResult(toolUseResult);
524
+ if (structuredBash &&
525
+ typeof structuredBash.stdout === "string" &&
526
+ typeof structuredBash.stderr === "string" &&
527
+ !structuredBash.isImage &&
528
+ structuredBash.backgroundTaskId === undefined) {
529
+ output = [structuredBash.stdout, structuredBash.stderr].filter(Boolean).join("\n");
530
+ // Two raw-text notices don't survive the structured stdout/stderr —
531
+ // re-establish them so the client isn't shown a clean-looking result:
532
+ // the CLI appends its abort marker only to the model-facing text, and
533
+ // an aborted command isn't a success, so synthesize a failing exit
534
+ // code when the result wasn't already an error.
535
+ if (structuredBash.interrupted) {
536
+ output = [output, "[Command was aborted before completion]"].filter(Boolean).join("\n");
537
+ exitCode = 1;
538
+ }
539
+ // Structured stdout is clipped (~30k chars) when the full output was
540
+ // persisted to disk; without this note the clip is silent and the
541
+ // path to the full output is lost.
542
+ if (typeof structuredBash.persistedOutputPath === "string") {
543
+ const size = typeof structuredBash.persistedOutputSize === "number"
544
+ ? ` (${structuredBash.persistedOutputSize} bytes total)`
545
+ : "";
546
+ output = [
547
+ output,
548
+ `[Output truncated${size}: full output saved to ${structuredBash.persistedOutputPath}]`,
549
+ ]
550
+ .filter(Boolean)
551
+ .join("\n");
552
+ }
553
+ }
554
+ else if (result &&
392
555
  typeof result === "object" &&
393
556
  "type" in result &&
394
557
  result.type === "bash_code_execution_result") {
@@ -448,6 +611,36 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
448
611
  }
449
612
  return {};
450
613
  }
614
+ case "Agent":
615
+ case "Task": {
616
+ // The raw tool_result text ends with a model-directed trailer (an
617
+ // `agentId: … (use SendMessage …)` line plus a `<usage>` totals block)
618
+ // that ACP clients shouldn't see. The message-level `tool_use_result`
619
+ // carries the structured AgentOutput whose `content` is the subagent's
620
+ // report without the trailer — render from it when present (per the SDK
621
+ // 0.3.207 guidance) and fall back to the raw text otherwise (older CLIs,
622
+ // replayed sessions).
623
+ // Narrowed to the full union, not the completed variant — the status
624
+ // check below is what discriminates it, and pre-narrowing would let
625
+ // future field reads typecheck against a variant the runtime value may
626
+ // not be.
627
+ const structured = structuredResult(toolUseResult);
628
+ if (structured?.status === "completed" &&
629
+ Array.isArray(structured.content) &&
630
+ // A completed subagent can end with zero text blocks; an empty
631
+ // structured render would beat the raw fallback for no benefit.
632
+ structured.content.length > 0) {
633
+ return toAcpContentUpdate(structured.content, "is_error" in toolResult ? toolResult.is_error : false);
634
+ }
635
+ // No structured report to render from (replayed sessions —
636
+ // getSessionMessages doesn't expose the transcript's toolUseResult —
637
+ // and older CLIs). The SDK advises rendering from tool_use_result
638
+ // instead of parsing the text, but with no structured value the
639
+ // tail-anchored strip is the only cleanup available; if the trailer
640
+ // format changes it simply stops matching and the full raw text
641
+ // renders, no worse than before.
642
+ return toAcpContentUpdate(stripAgentTrailerFromContent(toolResult.content), "is_error" in toolResult ? toolResult.is_error : false);
643
+ }
451
644
  case "Edit": // Edit is handled in hooks
452
645
  case "Write": {
453
646
  return {};
@@ -455,11 +648,47 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
455
648
  case "ExitPlanMode": {
456
649
  return { title: "Exited Plan Mode" };
457
650
  }
651
+ case "WebSearch": {
652
+ // The raw tool_result text is a model-directed dump ("Web search
653
+ // results for query: …\n\nLinks: [{…json…}]"). The structured
654
+ // WebSearchOutput carries the hits — render them the way server-side
655
+ // web_search_result blocks render ("Title (url)").
656
+ const structuredSearch = structuredResult(toolUseResult);
657
+ if (structuredSearch && Array.isArray(structuredSearch.results)) {
658
+ const lines = structuredSearch.results.flatMap((entry) => typeof entry === "string"
659
+ ? [entry]
660
+ : Array.isArray(entry?.content)
661
+ ? // tool_use_result arrives untyped across CLI version skew —
662
+ // skip off-spec hits rather than rendering
663
+ // "undefined (undefined)" lines.
664
+ entry.content.flatMap((hit) => typeof hit?.title === "string" && typeof hit?.url === "string"
665
+ ? [formatWebSearchHit(hit)]
666
+ : [])
667
+ : []);
668
+ if (lines.length > 0) {
669
+ return {
670
+ content: [
671
+ {
672
+ type: "content",
673
+ content: { type: "text", text: lines.join("\n") },
674
+ },
675
+ ],
676
+ };
677
+ }
678
+ }
679
+ return rawContentUpdate();
680
+ }
458
681
  default: {
459
- return toAcpContentUpdate(toolResult.content, "is_error" in toolResult ? toolResult.is_error : false);
682
+ return rawContentUpdate();
460
683
  }
461
684
  }
462
685
  }
686
+ /** One display format for a web-search hit, shared by the structured
687
+ * WebSearchOutput render and the server-side `web_search_result` block so
688
+ * the two paths can't drift. */
689
+ function formatWebSearchHit(hit) {
690
+ return `${hit.title} (${hit.url})`;
691
+ }
463
692
  function toAcpContentUpdate(content, isError = false) {
464
693
  if (Array.isArray(content) && content.length > 0) {
465
694
  return {
@@ -524,7 +753,7 @@ function toAcpContentBlock(content, isError) {
524
753
  case "tool_search_tool_result_error":
525
754
  return wrapText(`Error: ${content.error_code}${content.error_message ? ` - ${content.error_message}` : ""}`);
526
755
  case "web_search_result":
527
- return wrapText(`${content.title} (${content.url})`);
756
+ return wrapText(formatWebSearchHit(content));
528
757
  case "web_search_tool_result_error":
529
758
  return wrapText(`Error: ${content.error_code}`);
530
759
  case "web_fetch_result":
@@ -698,7 +927,7 @@ export const registerHookCallback = (toolUseID, { onPostToolUseHook, }) => {
698
927
  };
699
928
  };
700
929
  /* A callback for Claude Code that is called when receiving a PostToolUse hook */
701
- export const createPostToolUseHook = (logger = console, options) => async (input, toolUseID) => {
930
+ export const createPostToolUseHook = (options) => async (input, toolUseID) => {
702
931
  if (input.hook_event_name === "PostToolUse") {
703
932
  // Handle EnterPlanMode tool - notify client of mode change after successful execution
704
933
  if (input.tool_name === "EnterPlanMode" && options?.onEnterPlanMode) {
@@ -708,12 +937,8 @@ export const createPostToolUseHook = (logger = console, options) => async (input
708
937
  const onPostToolUseHook = toolUseCallbacks[toolUseID]?.onPostToolUseHook;
709
938
  if (onPostToolUseHook) {
710
939
  await onPostToolUseHook(toolUseID, input.tool_input, input.tool_response);
711
- delete toolUseCallbacks[toolUseID]; // Cleanup after execution
712
- }
713
- else {
714
- logger.error(`No onPostToolUseHook found for tool use ID: ${toolUseID}`);
715
- delete toolUseCallbacks[toolUseID];
716
940
  }
941
+ delete toolUseCallbacks[toolUseID]; // Cleanup after execution
717
942
  }
718
943
  }
719
944
  return { continue: true };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.58.1",
6
+ "version": "0.60.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",
@@ -64,20 +64,20 @@
64
64
  },
65
65
  "dependencies": {
66
66
  "@agentclientprotocol/sdk": "1.2.1",
67
- "@anthropic-ai/claude-agent-sdk": "0.3.205",
67
+ "@anthropic-ai/claude-agent-sdk": "0.3.215",
68
68
  "zod": "^3.25.0 || ^4.0.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@anthropic-ai/sdk": "0.110.0",
71
+ "@anthropic-ai/sdk": "0.112.3",
72
72
  "@eslint/js": "10.0.1",
73
73
  "@tsconfig/node22": "22.0.5",
74
74
  "@types/node": "26.1.1",
75
- "@typescript-eslint/eslint-plugin": "8.63.0",
76
- "@typescript-eslint/parser": "8.63.0",
77
- "eslint": "10.6.0",
75
+ "@typescript-eslint/eslint-plugin": "8.64.0",
76
+ "@typescript-eslint/parser": "8.64.0",
77
+ "eslint": "10.7.0",
78
78
  "eslint-config-prettier": "10.1.8",
79
79
  "globals": "17.7.0",
80
- "prettier": "3.9.4",
80
+ "prettier": "3.9.5",
81
81
  "ts-node": "10.9.2",
82
82
  "typescript": "6.0.3",
83
83
  "vitest": "4.1.10"