mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 04:30:59 +00:00
feat(deps): bump @anthropic-ai/claude-agent-sdk to 0.3.207 (#874)
Bumps the minor group with 14 updates: | Package | From | To | | --- | --- | --- | | [@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript) | `0.3.206` | `0.3.207` | | [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript) | `0.110.0` | `0.111.0` | | [eslint](https://github.com/eslint/eslint) | `10.6.0` | `10.7.0` | | @anthropic-ai/claude-agent-sdk-darwin-arm64 | `0.3.206` | `0.3.207` | | @anthropic-ai/claude-agent-sdk-darwin-x64 | `0.3.206` | `0.3.207` | | @anthropic-ai/claude-agent-sdk-linux-arm64 | `0.3.206` | `0.3.207` | | @anthropic-ai/claude-agent-sdk-linux-arm64-musl | `0.3.206` | `0.3.207` | | @anthropic-ai/claude-agent-sdk-linux-x64 | `0.3.206` | `0.3.207` | | @anthropic-ai/claude-agent-sdk-linux-x64-musl | `0.3.206` | `0.3.207` | | @anthropic-ai/claude-agent-sdk-win32-arm64 | `0.3.206` | `0.3.207` | | @anthropic-ai/claude-agent-sdk-win32-x64 | `0.3.206` | `0.3.207` | | [es-module-lexer](https://github.com/guybedford/es-module-lexer) | `2.3.0` | `2.3.1` | | [hono](https://github.com/honojs/hono) | `4.12.29` | `4.12.30` | | [postcss](https://github.com/postcss/postcss) | `8.5.16` | `8.5.19` | Updates `@anthropic-ai/claude-agent-sdk` from 0.3.206 to 0.3.207 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/anthropics/claude-agent-sdk-typescript/releases">@anthropic-ai/claude-agent-sdk's releases</a>.</em></p> <blockquote> <h2>v0.3.207</h2> <h2>What's changed</h2> <ul> <li>Fixed <code>canUseTool</code> returning <code>{behavior: 'allow'}</code> without <code>updatedInput</code> being rejected as a deny with a raw ZodError message; the tool now runs with the original input per the documented contract</li> <li>The Agent tool's structured result now has a published SDK type (<code>AgentToolCompletedOutput</code>) that matches the emitted object exactly</li> </ul> <h2>Update</h2> <pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.207 # or yarn add @anthropic-ai/claude-agent-sdk@0.3.207 # or pnpm add @anthropic-ai/claude-agent-sdk@0.3.207 # or bun add @anthropic-ai/claude-agent-sdk@0.3.207 </code></pre> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/anthropics/claude-agent-sdk-typescript/blob/main/CHANGELOG.md">@anthropic-ai/claude-agent-sdk's changelog</a>.</em></p> <blockquote> <h2>0.3.207</h2> <ul> <li>Fixed <code>canUseTool</code> returning <code>{behavior: 'allow'}</code> without <code>updatedInput</code> being rejected as a deny with a raw ZodError message; the tool now runs with the original input per the documented contract</li> <li>The Agent tool's structured result now has a published SDK type (<code>AgentToolCompletedOutput</code>) that matches the emitted object exactly</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="79b6350e13"><code>79b6350</code></a> chore: Update CHANGELOG.md</li> <li>See full diff in <a href="https://github.com/anthropics/claude-agent-sdk-typescript/compare/v0.3.206...v0.3.207">compare view</a></li> </ul> </details> <br /> Updates `@anthropic-ai/sdk` from 0.110.0 to 0.111.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/anthropics/anthropic-sdk-typescript/releases">@anthropic-ai/sdk's releases</a>.</em></p> <blockquote> <h2>sdk: v0.111.0</h2> <h2>0.111.0 (2026-07-10)</h2> <p>Full Changelog: <a href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.110.0...sdk-v0.111.0">sdk-v0.110.0...sdk-v0.111.0</a></p> <h3>Features</h3> <ul> <li><strong>api:</strong> add support for dreaming (<a href="77b28a6472">77b28a6</a>)</li> <li><strong>tools:</strong> gate session tool calls on evaluated_permission; bound idle by server stop_reason (<a href="68a6d7b92a">68a6d7b</a>)</li> </ul> <h3>Chores</h3> <ul> <li><strong>docs:</strong> small updates to field descriptions (<a href="e25b885eac">e25b885</a>)</li> <li><strong>docs:</strong> update model example (<a href="a33f3f0b7c">a33f3f0</a>)</li> <li><strong>docs:</strong> updates to descriptions and examples (<a href="eac4bace32">eac4bac</a>)</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/anthropics/anthropic-sdk-typescript/blob/main/CHANGELOG.md">@anthropic-ai/sdk's changelog</a>.</em></p> <blockquote> <h2>0.111.0 (2026-07-10)</h2> <p>Full Changelog: <a href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.110.0...sdk-v0.111.0">sdk-v0.110.0...sdk-v0.111.0</a></p> <h3>Features</h3> <ul> <li><strong>api:</strong> add support for dreaming (<a href="77b28a6472">77b28a6</a>)</li> <li><strong>tools:</strong> gate session tool calls on evaluated_permission; bound idle by server stop_reason (<a href="68a6d7b92a">68a6d7b</a>)</li> </ul> <h3>Chores</h3> <ul> <li><strong>docs:</strong> small updates to field descriptions (<a href="e25b885eac">e25b885</a>)</li> <li><strong>docs:</strong> update model example (<a href="a33f3f0b7c">a33f3f0</a>)</li> <li><strong>docs:</strong> updates to descriptions and examples (<a href="eac4bace32">eac4bac</a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="9e46760688"><code>9e46760</code></a> chore: release main</li> <li><a href="8d461ea962"><code>8d461ea</code></a> feat(api): add support for dreaming</li> <li><a href="9436e29159"><code>9436e29</code></a> codegen metadata</li> <li><a href="0aec1e748b"><code>0aec1e7</code></a> feat(tools): gate session tool calls on evaluated_permission; bound idle by s...</li> <li><a href="ac2fc6779d"><code>ac2fc67</code></a> chore(docs): update model example</li> <li><a href="c8af65d6af"><code>c8af65d</code></a> chore(docs): updates to descriptions and examples</li> <li><a href="2a3a9042ab"><code>2a3a904</code></a> codegen metadata</li> <li><a href="57c56c9172"><code>57c56c9</code></a> chore(docs): small updates to field descriptions</li> <li>See full diff in <a href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.110.0...sdk-v0.111.0">compare view</a></li> </ul> </details> <br /> Updates `eslint` from 10.6.0 to 10.7.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/eslint/eslint/releases">eslint's releases</a>.</em></p> <blockquote> <h2>v10.7.0</h2> <h2>Features</h2> <ul> <li><a href="cf2a9bf2d9"><code>cf2a9bf</code></a> feat: add errorClassNames option to preserve-caught-error rule (<a href="https://redirect.github.com/eslint/eslint/issues/21032">#21032</a>) (sethamus)</li> <li><a href="f8b873aae5"><code>f8b873a</code></a> feat: max-nested-callbacks option for constructor callbacks (<a href="https://redirect.github.com/eslint/eslint/issues/21063">#21063</a>) (fnx)</li> <li><a href="557fde8bc6"><code>557fde8</code></a> feat: support computed <code>Number.parseInt</code> member access in <code>radix</code> rule (<a href="https://redirect.github.com/eslint/eslint/issues/21041">#21041</a>) (Pixel)</li> <li><a href="0b4a73b658"><code>0b4a73b</code></a> feat: add suggestions to no-compare-neg-zero (<a href="https://redirect.github.com/eslint/eslint/issues/21034">#21034</a>) (den$)</li> <li><a href="96cdd427db"><code>96cdd42</code></a> feat: report invalid signed numeric radix values in <code>radix</code> rule (<a href="https://redirect.github.com/eslint/eslint/issues/21030">#21030</a>) (Pixel)</li> </ul> <h2>Bug Fixes</h2> <ul> <li><a href="3e7bf15e69"><code>3e7bf15</code></a> fix: apply <code>ignoreClassesWithImplements</code> to class expressions (<a href="https://redirect.github.com/eslint/eslint/issues/21069">#21069</a>) (Pixel)</li> <li><a href="0d7d70cf01"><code>0d7d70c</code></a> fix: insert cause outside wrapping parens in preserve-caught-error (<a href="https://redirect.github.com/eslint/eslint/issues/21062">#21062</a>) (Mahin Anowar)</li> <li><a href="75ec753226"><code>75ec753</code></a> fix: handle static template literals in <code>eqeqeq</code> rule (<a href="https://redirect.github.com/eslint/eslint/issues/21058">#21058</a>) (Pixel)</li> <li><a href="b717a22e24"><code>b717a22</code></a> fix: prevent <code>eqeqeq</code> null option from reporting non-equality operators (<a href="https://redirect.github.com/eslint/eslint/issues/21057">#21057</a>) (Pixel)</li> <li><a href="e35b05f196"><code>e35b05f</code></a> fix: avoid <code>no-invalid-regexp</code> false positive for shadowed RegExp (<a href="https://redirect.github.com/eslint/eslint/issues/21051">#21051</a>) (Pixel)</li> <li><a href="a3172b69c7"><code>a3172b6</code></a> fix: avoid <code>no-control-regex</code> false positive for shadowed RegExp (<a href="https://redirect.github.com/eslint/eslint/issues/21050">#21050</a>) (Pixel)</li> <li><a href="d1f637eca2"><code>d1f637e</code></a> fix: parenthesize sequence expression operands in no-implicit-coercion (<a href="https://redirect.github.com/eslint/eslint/issues/21045">#21045</a>) (spokodev)</li> <li><a href="8859bafb01"><code>8859baf</code></a> fix: avoid prefer-numeric-literals false positive for shadowed globals (<a href="https://redirect.github.com/eslint/eslint/issues/21047">#21047</a>) (한국)</li> <li><a href="a9e5961050"><code>a9e5961</code></a> fix: use-isnan false positive on shadowed NaN/Number (<a href="https://redirect.github.com/eslint/eslint/issues/20958">#20958</a>) (sethamus)</li> <li><a href="8a240a7610"><code>8a240a7</code></a> fix: avoid false positives in <code>radix</code> rule for spread arguments (<a href="https://redirect.github.com/eslint/eslint/issues/21044">#21044</a>) (Pixel)</li> </ul> <h2>Documentation</h2> <ul> <li><a href="c30d80801c"><code>c30d808</code></a> docs: Update README (GitHub Actions Bot)</li> <li><a href="5139800b23"><code>5139800</code></a> docs: document ESLint migration codemods in v9 and v10 guides (<a href="https://redirect.github.com/eslint/eslint/issues/20980">#20980</a>) (Alex Bit)</li> <li><a href="04174cbfaa"><code>04174cb</code></a> docs: Update README (GitHub Actions Bot)</li> <li><a href="026e130408"><code>026e130</code></a> docs: update semver policy for bug fixes (<a href="https://redirect.github.com/eslint/eslint/issues/21048">#21048</a>) (Milos Djermanovic)</li> <li><a href="9d42fefb0e"><code>9d42fef</code></a> docs: Update README (GitHub Actions Bot)</li> <li><a href="b23015955c"><code>b230159</code></a> docs: Update README (GitHub Actions Bot)</li> <li><a href="0129972ba1"><code>0129972</code></a> docs: correct <code>**/.js</code> glob to <code>**/*.js</code> in config files guide (<a href="https://redirect.github.com/eslint/eslint/issues/21036">#21036</a>) (EduardF1)</li> </ul> <h2>Chores</h2> <ul> <li><a href="948937986f"><code>9489379</code></a> chore: update dependency <code>@eslint/eslintrc</code> to ^3.3.6 (<a href="https://redirect.github.com/eslint/eslint/issues/21076">#21076</a>) (renovate[bot])</li> <li><a href="81a4774a92"><code>81a4774</code></a> chore: updates for v9.39.5 release (Jenkins)</li> <li><a href="9835414913"><code>9835414</code></a> chore: enable <code>$ExpectType</code> annotations in all TypeScript files (<a href="https://redirect.github.com/eslint/eslint/issues/21071">#21071</a>) (Francesco Trotta)</li> <li><a href="72adf6bd21"><code>72adf6b</code></a> chore: restrict <code>markdownlint-cli2</code> updates in renovate (<a href="https://redirect.github.com/eslint/eslint/issues/21067">#21067</a>) (lumir)</li> <li><a href="833ec10fd7"><code>833ec10</code></a> chore: update dependency prettier to v3.9.4 (<a href="https://redirect.github.com/eslint/eslint/issues/21061">#21061</a>) (renovate[bot])</li> <li><a href="7ea106ddcb"><code>7ea106d</code></a> chore: update ecosystem plugins (<a href="https://redirect.github.com/eslint/eslint/issues/21059">#21059</a>) (ESLint Bot)</li> <li><a href="8fb550e7c2"><code>8fb550e</code></a> chore: add prettier update commit to <code>.git-blame-ignore-revs</code> (<a href="https://redirect.github.com/eslint/eslint/issues/21056">#21056</a>) (lumir)</li> <li><a href="e4e11668ea"><code>e4e1166</code></a> chore: update dependency prettier to v3.9.1 (<a href="https://redirect.github.com/eslint/eslint/issues/21055">#21055</a>) (renovate[bot])</li> <li><a href="0493f53f3f"><code>0493f53</code></a> chore: update prettier to v3.9.0 (<a href="https://redirect.github.com/eslint/eslint/issues/21054">#21054</a>) (Pixel)</li> <li><a href="1056a996c6"><code>1056a99</code></a> chore: update dependency prettier to v3.8.5 (<a href="https://redirect.github.com/eslint/eslint/issues/21049">#21049</a>) (renovate[bot])</li> <li><a href="4d4155df4a"><code>4d4155d</code></a> ci: run ecosystem tests on pull requests (<a href="https://redirect.github.com/eslint/eslint/issues/21027">#21027</a>) (sethamus)</li> <li><a href="993539fd45"><code>993539f</code></a> chore: update dependency <code>@eslint/json</code> to ^2.0.1 (<a href="https://redirect.github.com/eslint/eslint/issues/21042">#21042</a>) (renovate[bot])</li> <li><a href="53f8b69c15"><code>53f8b69</code></a> test: add error locations to <code>no-constant-binary-expression</code> (<a href="https://redirect.github.com/eslint/eslint/issues/21039">#21039</a>) (lumir)</li> <li><a href="5ab71d52f9"><code>5ab71d5</code></a> refactor: clean up radix rule internals (<a href="https://redirect.github.com/eslint/eslint/issues/21015">#21015</a>) (Pixel)</li> <li><a href="a80a9a4614"><code>a80a9a4</code></a> chore: update ecosystem plugins (<a href="https://redirect.github.com/eslint/eslint/issues/21035">#21035</a>) (ESLint Bot)</li> <li><a href="7c9a02933c"><code>7c9a029</code></a> ci: add Node.js 26 to CI (<a href="https://redirect.github.com/eslint/eslint/issues/20847">#20847</a>) (lumir)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="fabd99bb74"><code>fabd99b</code></a> 10.7.0</li> <li><a href="37c5e75949"><code>37c5e75</code></a> Build: changelog update for 10.7.0</li> <li><a href="948937986f"><code>9489379</code></a> chore: update dependency <code>@eslint/eslintrc</code> to ^3.3.6 (<a href="https://redirect.github.com/eslint/eslint/issues/21076">#21076</a>)</li> <li><a href="81a4774a92"><code>81a4774</code></a> chore: updates for v9.39.5 release</li> <li><a href="3e7bf15e69"><code>3e7bf15</code></a> fix: apply <code>ignoreClassesWithImplements</code> to class expressions (<a href="https://redirect.github.com/eslint/eslint/issues/21069">#21069</a>)</li> <li><a href="9835414913"><code>9835414</code></a> chore: enable <code>$ExpectType</code> annotations in all TypeScript files (<a href="https://redirect.github.com/eslint/eslint/issues/21071">#21071</a>)</li> <li><a href="cf2a9bf2d9"><code>cf2a9bf</code></a> feat: add errorClassNames option to preserve-caught-error rule (<a href="https://redirect.github.com/eslint/eslint/issues/21032">#21032</a>)</li> <li><a href="c30d80801c"><code>c30d808</code></a> docs: Update README</li> <li><a href="f8b873aae5"><code>f8b873a</code></a> feat: max-nested-callbacks option for constructor callbacks (<a href="https://redirect.github.com/eslint/eslint/issues/21063">#21063</a>)</li> <li><a href="72adf6bd21"><code>72adf6b</code></a> chore: restrict <code>markdownlint-cli2</code> updates in renovate (<a href="https://redirect.github.com/eslint/eslint/issues/21067">#21067</a>)</li> <li>Additional commits viewable in <a href="https://github.com/eslint/eslint/compare/v10.6.0...v10.7.0">compare view</a></li> </ul> </details> <br /> Updates `@anthropic-ai/claude-agent-sdk-darwin-arm64` from 0.3.206 to 0.3.207 Updates `@anthropic-ai/claude-agent-sdk-darwin-x64` from 0.3.206 to 0.3.207 Updates `@anthropic-ai/claude-agent-sdk-linux-arm64` from 0.3.206 to 0.3.207 Updates `@anthropic-ai/claude-agent-sdk-linux-arm64-musl` from 0.3.206 to 0.3.207 Updates `@anthropic-ai/claude-agent-sdk-linux-x64` from 0.3.206 to 0.3.207 Updates `@anthropic-ai/claude-agent-sdk-linux-x64-musl` from 0.3.206 to 0.3.207 Updates `@anthropic-ai/claude-agent-sdk-win32-arm64` from 0.3.206 to 0.3.207 Updates `@anthropic-ai/claude-agent-sdk-win32-x64` from 0.3.206 to 0.3.207 Updates `es-module-lexer` from 2.3.0 to 2.3.1 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/guybedford/es-module-lexer/releases">es-module-lexer's releases</a>.</em></p> <blockquote> <h2>2.3.1</h2> <h2>What's Changed</h2> <ul> <li>perf: fast native UTF-16 source copy into Wasm memory on Node by <a href="https://github.com/guybedford"><code>@guybedford</code></a> in <a href="https://redirect.github.com/guybedford/es-module-lexer/pull/224">guybedford/es-module-lexer#224</a></li> <li>perf(lexer): gate keywordStart on the next byte for i/c tokens by <a href="https://github.com/BridgeAR"><code>@BridgeAR</code></a> in <a href="https://redirect.github.com/guybedford/es-module-lexer/pull/223">guybedford/es-module-lexer#223</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/guybedford/es-module-lexer/compare/2.3.0...2.3.1">https://github.com/guybedford/es-module-lexer/compare/2.3.0...2.3.1</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="2b2e6209ba"><code>2b2e620</code></a> 2.3.1</li> <li><a href="8c21b74b1f"><code>8c21b74</code></a> perf(lexer): gate keywordStart on the next byte for i/c tokens (<a href="https://redirect.github.com/guybedford/es-module-lexer/issues/223">#223</a>)</li> <li><a href="b8f9d89f9d"><code>b8f9d89</code></a> perf: fast native UTF-16 source copy into Wasm memory on Node (<a href="https://redirect.github.com/guybedford/es-module-lexer/issues/224">#224</a>)</li> <li>See full diff in <a href="https://github.com/guybedford/es-module-lexer/compare/2.3.0...2.3.1">compare view</a></li> </ul> </details> <br /> Updates `hono` from 4.12.29 to 4.12.30 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/honojs/hono/releases">hono's releases</a>.</em></p> <blockquote> <h2>v4.12.30</h2> <h2>What's Changed</h2> <ul> <li>chore(benchmark/routers): bump deps in <a href="https://redirect.github.com/honojs/hono/pull/5107">honojs/hono#5107</a></li> <li>chore(benchmark): remove not used benchmarks in <a href="https://redirect.github.com/honojs/hono/pull/5108">honojs/hono#5108</a></li> <li>chore: update to ts6 in prep for ts7 in <a href="https://redirect.github.com/honojs/hono/pull/5104">honojs/hono#5104</a></li> <li>fix(cache): deduplicate Cache-Control directives case-insensitively in <a href="https://redirect.github.com/honojs/hono/pull/5025">honojs/hono#5025</a></li> <li>fix(compress): do not compress 206 Partial Content responses in <a href="https://redirect.github.com/honojs/hono/pull/5020">honojs/hono#5020</a></li> <li>fix(client): replaceUrlParam should not match a param that prefixes another in <a href="https://redirect.github.com/honojs/hono/pull/5096">honojs/hono#5096</a></li> <li>fix(method-override): set duplex when forwarding a stream body in query mode in <a href="https://redirect.github.com/honojs/hono/pull/5110">honojs/hono#5110</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/honojs/hono/compare/v4.12.29...v4.12.30">https://github.com/honojs/hono/compare/v4.12.29...v4.12.30</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="b2ae3a2204"><code>b2ae3a2</code></a> 4.12.30</li> <li><a href="e0cdeb0a89"><code>e0cdeb0</code></a> fix(method-override): set duplex when forwarding a stream body in query mode ...</li> <li><a href="653025e5c3"><code>653025e</code></a> fix(client): replaceUrlParam should not match a param that prefixes another (...</li> <li><a href="67efb27f7b"><code>67efb27</code></a> fix(compress): do not compress 206 Partial Content responses (<a href="https://redirect.github.com/honojs/hono/issues/5020">#5020</a>)</li> <li><a href="a48c8bf248"><code>a48c8bf</code></a> fix(cache): deduplicate Cache-Control directives case-insensitively (<a href="https://redirect.github.com/honojs/hono/issues/5025">#5025</a>)</li> <li><a href="3bb9a0e3a7"><code>3bb9a0e</code></a> chore: update to ts6 in prep for ts7 (<a href="https://redirect.github.com/honojs/hono/issues/5104">#5104</a>)</li> <li><a href="d3f97caa29"><code>d3f97ca</code></a> chore(benchmark): remove not used benchmarks (<a href="https://redirect.github.com/honojs/hono/issues/5108">#5108</a>)</li> <li><a href="29a3f73fd5"><code>29a3f73</code></a> chore(benchmark/routers): bump deps (<a href="https://redirect.github.com/honojs/hono/issues/5107">#5107</a>)</li> <li>See full diff in <a href="https://github.com/honojs/hono/compare/v4.12.29...v4.12.30">compare view</a></li> </ul> </details> <br /> Updates `postcss` from 8.5.16 to 8.5.19 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/postcss/postcss/releases">postcss's releases</a>.</em></p> <blockquote> <h2>8.5.19</h2> <ul> <li>Fixed cleaning <code>before</code> for new nodes inserted to <code>Root</code> (by <a href="https://github.com/MahinAnowar"><code>@MahinAnowar</code></a>).</li> </ul> <h2>8.5.18</h2> <ul> <li>Restricted loading previous source maps file to the <code>opts.from</code> folder for security reasons (use <code>unsafeMap: true</code> to disable the check).</li> </ul> <h2>8.5.17</h2> <ul> <li>Fixed <code>Maximum call stack size exceeded</code> error.</li> <li>Fixed Prototype hijacking for <code>postcss.fromJSON()</code>.</li> <li>Fixed <code>Input#origin()</code> for unmapped end position (by <a href="https://github.com/chatman-media"><code>@chatman-media</code></a>).</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's changelog</a>.</em></p> <blockquote> <h2>8.5.19</h2> <ul> <li>Fixed cleaning <code>before</code> for new nodes inserted to <code>Root</code> (by <a href="https://github.com/MahinAnowar"><code>@MahinAnowar</code></a>).</li> </ul> <h2>8.5.18</h2> <ul> <li>Restricted loading previous source maps file to the <code>opts.from</code> folder for security reasons (use <code>unsafeMap: true</code> to disable the check).</li> </ul> <h2>8.5.17</h2> <ul> <li>Fixed <code>Maximum call stack size exceeded</code> error.</li> <li>Fixed Prototype hijacking for <code>postcss.fromJSON()</code>.</li> <li>Fixed <code>Input#origin()</code> for unmapped end position (by <a href="https://github.com/chatman-media"><code>@chatman-media</code></a>).</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="9543b22769"><code>9543b22</code></a> Release 8.5.19 version</li> <li><a href="3d13bf9360"><code>3d13bf9</code></a> Fix CI on Windows too</li> <li><a href="00d0dd2322"><code>00d0dd2</code></a> Keep explicitly set raws.before when inserting nodes into root (<a href="https://redirect.github.com/postcss/postcss/issues/2111">#2111</a>)</li> <li><a href="7a05b33e7a"><code>7a05b33</code></a> Temporary fix CI</li> <li><a href="4c0d194c13"><code>4c0d194</code></a> Release 8.5.18 version</li> <li><a href="92b4e7891e"><code>92b4e78</code></a> Update dependencies</li> <li><a href="95663d3eb7"><code>95663d3</code></a> Limit where source map can be loaded for security reasons</li> <li><a href="74e25ae9f4"><code>74e25ae</code></a> Release 8.5.17 version</li> <li><a href="d1518afd5a"><code>d1518af</code></a> Fix Maximum call stack size exceeded error</li> <li><a href="2421312ffe"><code>2421312</code></a> Fix linter</li> <li>Additional commits viewable in <a href="https://github.com/postcss/postcss/compare/8.5.16...8.5.19">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
106
package-lock.json
generated
106
package-lock.json
generated
@@ -10,20 +10,20 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "1.2.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.206",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.207",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"claude-agent-acp": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.110.0",
|
||||
"@anthropic-ai/sdk": "0.111.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.5",
|
||||
"@types/node": "26.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.63.0",
|
||||
"@typescript-eslint/parser": "8.63.0",
|
||||
"eslint": "10.6.0",
|
||||
"eslint": "10.7.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.7.0",
|
||||
"prettier": "3.9.5",
|
||||
@@ -45,22 +45,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.206",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.206.tgz",
|
||||
"integrity": "sha512-KljDh9Pg4YCYpoXS8dnWoVSsOHtU4yLCW268K2iOruSxFXE8/Tay6DPvmJzYuqjP5YLNYfj05ZGykwZSUn6GXA==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.207.tgz",
|
||||
"integrity": "sha512-y0PkQRmQBi96MHiN5Xzfq+GaddxCZCqI/cXEQBLYBLXGa4i1nDSlulQqkMBj2RorrrSGQJ6Wdw+uhu6OfHNPzA==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.206",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.206",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.206",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.206",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.206",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.206",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.206",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.206"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.207",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.207"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -69,9 +69,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.206",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.206.tgz",
|
||||
"integrity": "sha512-FL2+NKcMMN47vcnCW2Fkt3AOgeRRlQxrisbPNaxrxqPJFzhUKs17x5j0XzLefd0xRbDAr74hd0PK/tnp6PHM6w==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.207.tgz",
|
||||
"integrity": "sha512-08xSo1FDx8h0aLhL5tvcRxa2SMmcUV3aDWeZiEJVTclyiDAs61BgTjAxCg+SZcu1CndjJO8cfO0yM5dhamxz3g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -82,9 +82,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.206",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.206.tgz",
|
||||
"integrity": "sha512-mRW8PPMfQN15EunLwpdmcVzk3XuM4DXQUM8DOzaeA1Hr1yxYPaVVBLr9hkdBKOqr8XTl3ueoTR6RZAy6a/n7OA==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.207.tgz",
|
||||
"integrity": "sha512-1o7K4EYqyCixZ/oeOZSh7AzSy6TM86xoOuf4VuORjPSS31hBnoqY0NGZd27+2VDs9LGtsdksmsTqcNGx9xd1hA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -95,9 +95,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.206",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.206.tgz",
|
||||
"integrity": "sha512-Id6H8l6EsGb7849EAZDOB4Ic+FQpbzt4D5HGOwp59CTW3o/1c4etjQA/Kl1K+DSEWn3FGo6D5UMVgc/rwhBj8g==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.207.tgz",
|
||||
"integrity": "sha512-X4uezYOifDiNTTmmugfRCdg3nNamrr1LFRY9hg30vWYTShL+bbN+nfC3KaFfSYCl4GTtsEEUbYdOTC2F3bBpcA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -111,9 +111,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.206",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.206.tgz",
|
||||
"integrity": "sha512-aMZe1Kl+kYv5QlA15W9Ae25MAzBsA9FA40f5TOtJebA+M/xliF0r2LLb2NdyBviiZCDllcE31l0zgYE0rQqFQw==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.207.tgz",
|
||||
"integrity": "sha512-oPj+g2DslhH4Y9nCTs7al4t9wZv78FZwLFQwOCg99BXuz1o0ZOpKmxyvR7J9eBR+GPszeMMS8gYplQTiZC9o2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -127,9 +127,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.206",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.206.tgz",
|
||||
"integrity": "sha512-egZhOC1RlEVhZyq6Oa1b04AF7hh4hO+8oCsJCZ3gOifJQuHih1oW3lZ8fOUxU85jlT2ytAnt5kRp4uQr6PJgbg==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.207.tgz",
|
||||
"integrity": "sha512-Kg6BPH8Ee0ny/oEUWJmvT1jCRBne4jVpRSOMsJcYp1Fav1rMEgpU219oJJs+LWwx4ifuuLtNWedqJNnVw7mnKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -143,9 +143,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.206",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.206.tgz",
|
||||
"integrity": "sha512-xQBOBhlcmTNc7YeYT5qLZOikpSq3WHlsJ/t6i7kJqUWrcXlOPaAoSMdKp5xO/V0CjAdzdJoWB88I55UAh8mclQ==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.207.tgz",
|
||||
"integrity": "sha512-uRv+D5oG/7EYr41FAJ9IPo2pZYBe2ZMaA6nSHCeizsgPxCSMtl5bNppmU21+jZJvo4hivObEgkGFERAhdGqygg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -159,9 +159,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.206",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.206.tgz",
|
||||
"integrity": "sha512-oRQk23bFXSz4QRhOxqnnvlLWq/KiF2PtSBYXrMg1AQrCOHJd2k66aOAS7AJ4ZSzejiyV4N6sS6UThfmF6ipHBQ==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.207.tgz",
|
||||
"integrity": "sha512-9fWpUzfkXlPAg2tf8JpQe7w9avFaomAUbfAwyAmykQgSIf66LwaJjvI5hNqhNqczRKyfsXPn3ei2S5HKlmFP+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -172,9 +172,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.206",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.206.tgz",
|
||||
"integrity": "sha512-BdjKmDojZjc5RjN+8Q6K7Yqf1WYhel6I3DkMXc9zdxS/xIuN42sx7zgQpdMGY73pm7ROPLqo3LieOeMhVH161w==",
|
||||
"version": "0.3.207",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.207.tgz",
|
||||
"integrity": "sha512-YPjVT0q6aXEM2MgN4CI6/9fqiTXwETji+4NoPOzCYuqAkhXZqp30Jsk7/NHqYGNNSfURKrsuAoliKB0rsbpbjg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -185,9 +185,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.110.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.110.0.tgz",
|
||||
"integrity": "sha512-hOP4bNYXDFHDxxiEgzlILXrxZIYCDnhe8sry0RDRKD/QnsEpvZcQpablCdm9X/WuD/YgOiSIkkqsL1mLLlTqJw==",
|
||||
"version": "0.111.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.111.0.tgz",
|
||||
"integrity": "sha512-1hUqKi+uJQoS5X90+InwHbFAXMvgq0DnsC5hVLEeSRaODiU5WvmqDAcVCmGS2wC0pN9Z8jtWCbWw7JLzeDdm/Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-schema-to-ts": "^3.1.1",
|
||||
@@ -1701,9 +1701,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz",
|
||||
"integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==",
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz",
|
||||
"integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -1741,9 +1741,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz",
|
||||
"integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==",
|
||||
"version": "10.7.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz",
|
||||
"integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -2371,9 +2371,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.12.29",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.29.tgz",
|
||||
"integrity": "sha512-1hNiRjawYrLq/4m3DQQjPGFg0VZkk4RjQJDff/excI6Dm9BiL75qxGrd7/c6YOxPdq6AscP3LiXhQ6fKFC1Waw==",
|
||||
"version": "4.12.30",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.30.tgz",
|
||||
"integrity": "sha512-emn+JoJjrN9YTpRDS5it/UI2SO9BAE37T6I3d963RxcZ81G9A4pr2SZTEiiaiKbzx+NKRg5BZ89fCL7gCJCUog==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -3188,9 +3188,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
|
||||
"integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
|
||||
"version": "8.5.19",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
|
||||
"integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
||||
@@ -64,17 +64,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "1.2.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.206",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.207",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.110.0",
|
||||
"@anthropic-ai/sdk": "0.111.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.5",
|
||||
"@types/node": "26.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.63.0",
|
||||
"@typescript-eslint/parser": "8.63.0",
|
||||
"eslint": "10.6.0",
|
||||
"eslint": "10.7.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.7.0",
|
||||
"prettier": "3.9.5",
|
||||
|
||||
@@ -3318,6 +3318,7 @@ export class ClaudeAcpAgent {
|
||||
taskState: session.taskState,
|
||||
emittedToolCalls: session.emittedToolCalls,
|
||||
messageId: messageIdForGrouping(message),
|
||||
toolUseResult: message.type === "user" ? message.tool_use_result : undefined,
|
||||
},
|
||||
)) {
|
||||
// sendUpdate records delivery. Subagent text/thinking is
|
||||
@@ -6302,6 +6303,11 @@ export function toAcpNotifications(
|
||||
// into a single message. Omit it (leave undefined) when unknown — never send
|
||||
// an explicit `null`.
|
||||
messageId?: string;
|
||||
// The SDK user message's `tool_use_result`: the structured Output object of
|
||||
// the tool_result this message carries (shape is per-tool). Used to render
|
||||
// Agent/Task results from the structured subagent report instead of the raw
|
||||
// text (which ends in a model-directed agentId/usage trailer).
|
||||
toolUseResult?: unknown;
|
||||
},
|
||||
): SessionNotification[] {
|
||||
const taskState = options?.taskState ?? new Map();
|
||||
@@ -6333,6 +6339,17 @@ export function toAcpNotifications(
|
||||
return [{ sessionId, update }];
|
||||
}
|
||||
|
||||
// `tool_use_result` is message-level and carries no tool_use_id of its own:
|
||||
// it describes "the" tool_result block of the message it rode in on. If
|
||||
// several tool_result blocks were ever batched into one message it couldn't
|
||||
// be attributed, so it is only honored when the message carries exactly one.
|
||||
const toolUseResult =
|
||||
options?.toolUseResult !== undefined &&
|
||||
content.filter((c) => typeof c === "object" && c !== null && c.type === "tool_result")
|
||||
.length === 1
|
||||
? options.toolUseResult
|
||||
: undefined;
|
||||
|
||||
const output = [];
|
||||
// Only handle the first chunk for streaming; extend as needed for batching
|
||||
for (const chunk of content) {
|
||||
@@ -6569,6 +6586,7 @@ export function toAcpNotifications(
|
||||
chunk,
|
||||
toolUseCache[chunk.tool_use_id],
|
||||
supportsTerminalOutput,
|
||||
toolUseResult,
|
||||
);
|
||||
|
||||
// When terminal output is supported, send terminal_output as a
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js";
|
||||
import { makeMockQuery } from "./helpers.js";
|
||||
|
||||
const { querySpy } = vi.hoisted(() => ({
|
||||
querySpy: vi.fn(),
|
||||
@@ -34,7 +35,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
const setModelSpy = vi.fn();
|
||||
querySpy.mockImplementation(({ options }: any) => {
|
||||
capturedOptions = options;
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
@@ -45,8 +46,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
],
|
||||
}),
|
||||
setModel: setModelSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
return { getCapturedOptions: () => capturedOptions, setModelSpy };
|
||||
}
|
||||
@@ -240,12 +240,11 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
const setPermissionModeSpy = vi.fn();
|
||||
querySpy.mockImplementation(({ options }: any) => {
|
||||
capturedOptions = options;
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({ models }),
|
||||
setModel: setModelSpy,
|
||||
setPermissionMode: setPermissionModeSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
return {
|
||||
getCapturedOptions: () => capturedOptions,
|
||||
@@ -396,11 +395,10 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
} {
|
||||
const setModelSpy = vi.fn();
|
||||
querySpy.mockImplementation(() => {
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({ models }),
|
||||
setModel: setModelSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
return { setModelSpy };
|
||||
}
|
||||
@@ -875,7 +873,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
|
||||
const setModelSpy = vi.fn();
|
||||
querySpy.mockImplementation(({ options: _options }: any) => {
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
@@ -891,8 +889,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
],
|
||||
}),
|
||||
setModel: setModelSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
|
||||
const { ClaudeAcpAgent } = await import("../acp-agent.js");
|
||||
@@ -923,7 +920,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
|
||||
const setModelSpy = vi.fn();
|
||||
querySpy.mockImplementation(() => {
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{ value: "default", displayName: "Default", description: "" },
|
||||
@@ -932,8 +929,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
],
|
||||
}),
|
||||
setModel: setModelSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -977,7 +973,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
|
||||
const setModelSpy = vi.fn();
|
||||
querySpy.mockImplementation(() => {
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{ value: "default", displayName: "Default", description: "" },
|
||||
@@ -985,8 +981,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
],
|
||||
}),
|
||||
setModel: setModelSpy,
|
||||
supportedCommands: async () => [],
|
||||
} as any;
|
||||
}) as any;
|
||||
});
|
||||
|
||||
const { ClaudeAcpAgent } = await import("../acp-agent.js");
|
||||
|
||||
@@ -7,30 +7,30 @@ import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-age
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
let capturedOptions: Options | undefined;
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk", async () => ({
|
||||
...(await vi.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
|
||||
const actual = await vi.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
|
||||
"@anthropic-ai/claude-agent-sdk",
|
||||
)),
|
||||
query: ({ options }: { options: Options }) => {
|
||||
capturedOptions = options;
|
||||
return {
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
value: "claude-sonnet-4-6",
|
||||
displayName: "Claude Sonnet",
|
||||
description: "Fast",
|
||||
supportsAutoMode: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
setModel: async () => {},
|
||||
setPermissionMode: async () => {},
|
||||
supportedCommands: async () => [],
|
||||
[Symbol.asyncIterator]: async function* () {},
|
||||
};
|
||||
},
|
||||
}));
|
||||
);
|
||||
const { makeMockQuery } = await import("./helpers.js");
|
||||
return {
|
||||
...actual,
|
||||
query: ({ options }: { options: Options }) => {
|
||||
capturedOptions = options;
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
value: "claude-sonnet-4-6",
|
||||
displayName: "Claude Sonnet",
|
||||
description: "Fast",
|
||||
supportsAutoMode: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
vi.mock("../tools.js", async () => ({
|
||||
...(await vi.importActual<typeof import("../tools.js")>("../tools.js")),
|
||||
registerHookCallback: vi.fn(),
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import { describe, expect, it, Mock, vi, afterEach, beforeEach } from "vitest";
|
||||
import { AcpClient, ClaudeAcpAgent } from "../acp-agent.js";
|
||||
import { makeMockQuery } from "./helpers.js";
|
||||
|
||||
const mockQuery = vi.hoisted(() =>
|
||||
vi.fn(() => ({
|
||||
initializationResult: vi.fn().mockResolvedValue({
|
||||
models: [
|
||||
{ value: "id", displayName: "name", description: "description", supportsAutoMode: true },
|
||||
],
|
||||
}),
|
||||
setModel: vi.fn(),
|
||||
setPermissionMode: vi.fn(),
|
||||
supportedCommands: vi.fn().mockResolvedValue([]),
|
||||
})),
|
||||
);
|
||||
const mockQuery = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk", async () => ({
|
||||
...(await vi.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
|
||||
@@ -24,6 +14,22 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => ({
|
||||
describe("authorization", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
// Set here (not in vi.fn(impl) at hoist time) so the helper import is
|
||||
// available; afterEach's resetAllMocks clears it, beforeEach re-sets it.
|
||||
mockQuery.mockImplementation(() =>
|
||||
makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
value: "id",
|
||||
displayName: "name",
|
||||
description: "description",
|
||||
supportsAutoMode: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -12,11 +12,12 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
|
||||
const actual = await vi.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
|
||||
"@anthropic-ai/claude-agent-sdk",
|
||||
);
|
||||
const { makeMockQuery, DEFAULT_CONTEXT_USAGE } = await import("./helpers.js");
|
||||
return {
|
||||
...actual,
|
||||
query: (args: { prompt: unknown; options: Options }) => {
|
||||
capturedOptions = args.options;
|
||||
return {
|
||||
return makeMockQuery({
|
||||
initializationResult: async () => ({
|
||||
models: [
|
||||
{
|
||||
@@ -27,15 +28,9 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
|
||||
},
|
||||
],
|
||||
}),
|
||||
setModel: async () => {},
|
||||
setPermissionMode: async () => {},
|
||||
supportedCommands: async () => [],
|
||||
getContextUsage: () =>
|
||||
contextUsageResult
|
||||
? contextUsageResult()
|
||||
: Promise.reject(new Error("no context usage mocked")),
|
||||
[Symbol.asyncIterator]: async function* () {},
|
||||
};
|
||||
contextUsageResult ? contextUsageResult() : Promise.resolve(DEFAULT_CONTEXT_USAGE),
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -520,8 +515,13 @@ describe("createSession options merging", () => {
|
||||
|
||||
it("ignores a non-numeric value", async () => {
|
||||
process.env.MAX_THINKING_TOKENS = "lots";
|
||||
// The bad value is deliberate — capture the agent's warning instead of
|
||||
// letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
expect(capturedOptions!.thinking).toBeUndefined();
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("MAX_THINKING_TOKENS"));
|
||||
});
|
||||
|
||||
it("lets a user-provided thinking option override the env default", async () => {
|
||||
@@ -637,11 +637,18 @@ describe("createSession options merging", () => {
|
||||
});
|
||||
|
||||
it("falls back to the default window when getContextUsage fails and inference misses", async () => {
|
||||
// The default mock rejects getContextUsage, and the mock model
|
||||
// ("claude-sonnet-4-6" / "Claude Sonnet" / "Fast") has no "1m" hint.
|
||||
// getContextUsage rejects, and the mock model ("claude-sonnet-4-6" /
|
||||
// "Claude Sonnet" / "Fast") has no "1m" hint.
|
||||
contextUsageResult = () => Promise.reject(new Error("no context usage mocked"));
|
||||
// The rejection is deliberate — capture the agent's warning instead of
|
||||
// letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
|
||||
const response = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
|
||||
expect(sessionFor(response.sessionId).contextWindowSize).toBe(200000);
|
||||
expect(errorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores a nonsensical (non-positive) reported window", async () => {
|
||||
@@ -750,6 +757,10 @@ describe("createSession options merging", () => {
|
||||
|
||||
it("cancels on a malformed payload without presenting anything", async () => {
|
||||
const { onUserDialog, createElicitation } = await setupDialog();
|
||||
// The malformed payload is deliberate — capture the agent's warning
|
||||
// instead of letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
|
||||
const result = await onUserDialog(
|
||||
{ dialogKind: "refusal_fallback_prompt", payload: { fallbackModel: 42 } },
|
||||
@@ -758,11 +769,16 @@ describe("createSession options merging", () => {
|
||||
|
||||
expect(result).toEqual({ behavior: "cancelled" });
|
||||
expect(createElicitation).not.toHaveBeenCalled();
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("unexpected shape"));
|
||||
});
|
||||
|
||||
it("cancels when the elicitation request fails", async () => {
|
||||
const { onUserDialog, createElicitation } = await setupDialog();
|
||||
createElicitation.mockRejectedValue(new Error("client exploded"));
|
||||
// The client failure is deliberate — capture the agent's warning
|
||||
// instead of letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
|
||||
const result = await onUserDialog(
|
||||
{
|
||||
@@ -773,6 +789,7 @@ describe("createSession options merging", () => {
|
||||
);
|
||||
|
||||
expect(result).toEqual({ behavior: "cancelled" });
|
||||
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("client exploded"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
34
src/tests/helpers.ts
Normal file
34
src/tests/helpers.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Shared test doubles. Deliberately vitest-free so `vi.mock` async factories
|
||||
* can `await import("./helpers.js")` without ordering hazards; tests supply
|
||||
* their own vi.fn spies via `overrides`.
|
||||
*/
|
||||
|
||||
/** The context-usage report the base mock query returns. `rawMaxTokens`
|
||||
* matches the agent's DEFAULT_CONTEXT_WINDOW so window-related assertions
|
||||
* don't shift in tests that don't care about context usage. */
|
||||
export const DEFAULT_CONTEXT_USAGE = { totalTokens: 0, rawMaxTokens: 200000 };
|
||||
|
||||
/**
|
||||
* Base stub for the SDK `query()` return object, covering the surface
|
||||
* ClaudeAcpAgent touches unconditionally at session creation. Tests pass
|
||||
* `overrides` for the parts they assert on (spies, custom models, rejecting
|
||||
* getContextUsage, …).
|
||||
*
|
||||
* When the agent starts calling a new SDK method on every session, add it
|
||||
* here once — the getContextUsage adoption required hand-editing ~10 inline
|
||||
* mocks across five files, and any missed copy didn't fail: it silently
|
||||
* rerouted that test through the error-fallback branch and re-polluted test
|
||||
* output.
|
||||
*/
|
||||
export function makeMockQuery(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
initializationResult: async () => ({ models: [] }),
|
||||
setModel: async () => {},
|
||||
setPermissionMode: async () => {},
|
||||
supportedCommands: async () => [],
|
||||
getContextUsage: async () => DEFAULT_CONTEXT_USAGE,
|
||||
[Symbol.asyncIterator]: async function* () {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { SessionNotification } from "@agentclientprotocol/sdk";
|
||||
import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js";
|
||||
import { makeMockQuery } from "./helpers.js";
|
||||
|
||||
const { registerHookCallbackSpy } = vi.hoisted(() => ({
|
||||
registerHookCallbackSpy: vi.fn(),
|
||||
@@ -101,12 +102,11 @@ describe("session config options", () => {
|
||||
applyFlagSettingsSpy = vi.fn();
|
||||
|
||||
(agent as unknown as { sessions: Record<string, unknown> }).sessions[SESSION_ID] = {
|
||||
query: {
|
||||
query: makeMockQuery({
|
||||
setPermissionMode: setPermissionModeSpy,
|
||||
setModel: setModelSpy,
|
||||
applyFlagSettings: applyFlagSettingsSpy,
|
||||
supportedCommands: async () => [],
|
||||
},
|
||||
}),
|
||||
input: null,
|
||||
cancelled: false,
|
||||
permissionMode: "default",
|
||||
@@ -956,6 +956,10 @@ describe("session config options", () => {
|
||||
session.modelInfos = session.modelInfos.map((m: ModelInfo) =>
|
||||
m.value === "claude-sonnet-4-6" ? { ...m, resolvedModel: "claude-sonnet-5[1m]" } : m,
|
||||
);
|
||||
// The rejection is deliberate — capture the agent's warning instead of
|
||||
// letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
@@ -964,12 +968,17 @@ describe("session config options", () => {
|
||||
});
|
||||
|
||||
expect(session.contextWindowSize).toBe(1_000_000);
|
||||
expect(errorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to the default window when the SDK and inference both miss", async () => {
|
||||
const session = getSession();
|
||||
session.contextWindowSize = 1_000_000;
|
||||
session.query.getContextUsage = vi.fn().mockRejectedValue(new Error("boom"));
|
||||
// The rejection is deliberate — capture the agent's warning instead of
|
||||
// letting it hit the console.
|
||||
const errorSpy = vi.fn();
|
||||
(agent as any).logger = { log: () => {}, error: errorSpy };
|
||||
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
@@ -978,6 +987,7 @@ describe("session config options", () => {
|
||||
});
|
||||
|
||||
expect(session.contextWindowSize).toBe(200000);
|
||||
expect(errorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps the learned window when re-asserting the current model", async () => {
|
||||
|
||||
@@ -2205,3 +2205,460 @@ describe("empty message content is not emitted", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Agent/Task tool_result rendering from tool_use_result", () => {
|
||||
const mockClient = {} as AcpClient;
|
||||
const mockLogger: Logger = { log: () => {}, error: () => {} };
|
||||
|
||||
const TRAILER =
|
||||
"\nagentId: a0e1eff08fcb6e2e8 (use SendMessage with to: 'a0e1eff08fcb6e2e8', summary: '<5-10 word recap>' to continue this agent)\n<usage>subagent_tokens: 11735\ntool_uses: 2\nduration_ms: 21237</usage>";
|
||||
|
||||
const agentToolUse = {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_agent",
|
||||
name: "Task",
|
||||
input: { description: "Explore", prompt: "look around" },
|
||||
};
|
||||
|
||||
const rawResult: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_agent",
|
||||
content: [{ type: "text", text: `The report.${TRAILER}` }],
|
||||
};
|
||||
|
||||
const structured = {
|
||||
status: "completed",
|
||||
agentId: "a0e1eff08fcb6e2e8",
|
||||
content: [{ type: "text", text: "The structured report." }],
|
||||
totalTokens: 11735,
|
||||
totalToolUseCount: 2,
|
||||
totalDurationMs: 21237,
|
||||
};
|
||||
|
||||
it("renders the structured subagent report instead of the raw trailer text", () => {
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false, structured);
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [{ type: "content", content: { type: "text", text: "The structured report." } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("strips the trailer from the raw fallback when tool_use_result is absent", () => {
|
||||
// Replayed sessions and older CLIs have no structured report; the
|
||||
// tail-anchored strip is the only cleanup available there.
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false);
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [{ type: "content", content: { type: "text", text: "The report." } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("strips only matching trailer parts and leaves unrecognized text alone", () => {
|
||||
const oddResult: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_agent",
|
||||
content: [
|
||||
{ type: "text", text: "Report A.\n<usage>subagent_tokens: 5</usage>" },
|
||||
{ type: "text", text: "Report B.\nagentId: abc-123 (for resuming)" },
|
||||
{ type: "text", text: "agentId mentioned mid-text (not a trailer) stays.\nDone." },
|
||||
],
|
||||
};
|
||||
const update = toolUpdateFromToolResult(oddResult, agentToolUse, false);
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{ type: "content", content: { type: "text", text: "Report A." } },
|
||||
{ type: "content", content: { type: "text", text: "Report B." } },
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: "agentId mentioned mid-text (not a trailer) stays.\nDone." },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back (trailer-stripped) when tool_use_result is the async_launched variant", () => {
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false, {
|
||||
status: "async_launched",
|
||||
agentId: "a0e1eff08fcb6e2e8",
|
||||
description: "Explore",
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{ type: "content", content: { type: "text", text: "The report." } },
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back (trailer-stripped) when the structured content array is empty", () => {
|
||||
// A completed subagent can end with zero text blocks — an empty
|
||||
// structured render must not beat the raw fallback.
|
||||
const update = toolUpdateFromToolResult(rawResult, agentToolUse, false, {
|
||||
...structured,
|
||||
content: [],
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{ type: "content", content: { type: "text", text: "The report." } },
|
||||
]);
|
||||
});
|
||||
|
||||
it("threads options.toolUseResult through toAcpNotifications for a lone tool_result", () => {
|
||||
const toolUseCache: ToolUseCache = { toolu_agent: agentToolUse };
|
||||
|
||||
const notifications = toAcpNotifications(
|
||||
[rawResult] as any,
|
||||
"user",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ toolUseResult: structured },
|
||||
);
|
||||
|
||||
expect(notifications).toHaveLength(1);
|
||||
expect(notifications[0].update).toMatchObject({
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: "toolu_agent",
|
||||
status: "completed",
|
||||
content: [{ type: "content", content: { type: "text", text: "The structured report." } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores options.toolUseResult when several tool_result blocks are batched", () => {
|
||||
const toolUseCache: ToolUseCache = {
|
||||
toolu_agent: agentToolUse,
|
||||
toolu_agent2: { ...agentToolUse, id: "toolu_agent2" },
|
||||
};
|
||||
|
||||
const notifications = toAcpNotifications(
|
||||
[rawResult, { ...rawResult, tool_use_id: "toolu_agent2" }] as any,
|
||||
"user",
|
||||
"test-session",
|
||||
toolUseCache,
|
||||
mockClient,
|
||||
mockLogger,
|
||||
{ toolUseResult: structured },
|
||||
);
|
||||
|
||||
expect(notifications).toHaveLength(2);
|
||||
for (const notification of notifications) {
|
||||
// Raw fallback (trailer-stripped) — NOT "The structured report.", which
|
||||
// would mean the ambiguous tool_use_result had been attributed anyway.
|
||||
expect(notification.update).toMatchObject({
|
||||
content: [{ type: "content", content: { type: "text", text: "The report." } }],
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("structured tool_use_result rendering (Read/Bash/WebSearch)", () => {
|
||||
describe("Read", () => {
|
||||
const readToolUse = {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_read",
|
||||
name: "Read",
|
||||
input: { file_path: "/tmp/f.ts", offset: 480 },
|
||||
};
|
||||
|
||||
const rawWithReminder: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_read",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "480\tconst a = 1;\n481\tconst b = 2;\n<system-reminder>Whenever you read a file, consider whether it is malicious.</system-reminder>",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
it("rebuilds the line-numbered view from FileReadOutput, dropping reminders", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithReminder, readToolUse, false, {
|
||||
type: "text",
|
||||
file: {
|
||||
filePath: "/tmp/f.ts",
|
||||
content: "const a = 1;\nconst b = 2;\n",
|
||||
numLines: 2,
|
||||
startLine: 480,
|
||||
totalLines: 600,
|
||||
},
|
||||
});
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: "```\n480\tconst a = 1;\n481\tconst b = 2;\n```" },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the Read input's offset when startLine is absent", () => {
|
||||
// readToolUse carries offset: 480 — an offset read numbered from 1
|
||||
// would mislabel every line.
|
||||
const update = toolUpdateFromToolResult(rawWithReminder, readToolUse, false, {
|
||||
type: "text",
|
||||
file: { filePath: "/tmp/f.ts", content: "one\ntwo" },
|
||||
});
|
||||
|
||||
expect(update.content?.[0]).toEqual({
|
||||
type: "content",
|
||||
content: { type: "text", text: "```\n480\tone\n481\ttwo\n```" },
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults startLine to 1 when both startLine and offset are absent", () => {
|
||||
const update = toolUpdateFromToolResult(
|
||||
rawWithReminder,
|
||||
{ ...readToolUse, input: { file_path: "/tmp/f.ts" } },
|
||||
false,
|
||||
{
|
||||
type: "text",
|
||||
file: { filePath: "/tmp/f.ts", content: "one\ntwo" },
|
||||
},
|
||||
);
|
||||
|
||||
expect(update.content?.[0]).toEqual({
|
||||
type: "content",
|
||||
content: { type: "text", text: "```\n1\tone\n2\ttwo\n```" },
|
||||
});
|
||||
});
|
||||
|
||||
it("appends a truncation note when truncatedByTokenCap is set", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithReminder, readToolUse, false, {
|
||||
type: "text",
|
||||
file: {
|
||||
filePath: "/tmp/f.ts",
|
||||
content: "one\ntwo\n",
|
||||
numLines: 2,
|
||||
startLine: 1,
|
||||
totalLines: 9000,
|
||||
truncatedByTokenCap: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(update.content?.[0]).toEqual({
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "```\n1\tone\n2\ttwo\n[File truncated: showing 2 of 9000 lines]\n```",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to raw content for non-text variants", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithReminder, readToolUse, false, {
|
||||
type: "image",
|
||||
file: { base64: "aGk=", type: "image/png", originalSize: 3 },
|
||||
});
|
||||
|
||||
// Raw path: markdown-escaped raw text (reminder included — image reads
|
||||
// don't carry reminders in practice).
|
||||
expect(update.content).toHaveLength(1);
|
||||
expect((update.content?.[0] as any).content.text).toContain("const a = 1;");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bash", () => {
|
||||
const bashToolUse = {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_bash",
|
||||
name: "Bash",
|
||||
input: { command: "git push" },
|
||||
};
|
||||
|
||||
const HINT =
|
||||
"\n[This command modified 1 file you've previously read: src/foo.ts. Call Read before editing.]";
|
||||
|
||||
const rawWithHint: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_bash",
|
||||
content: `pushed ok${HINT}`,
|
||||
};
|
||||
|
||||
const structured = {
|
||||
stdout: "pushed ok",
|
||||
stderr: "",
|
||||
interrupted: false,
|
||||
isImage: false,
|
||||
};
|
||||
|
||||
it("prefers structured stdout/stderr over raw text with model-directed hints", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithHint, bashToolUse, true, structured);
|
||||
|
||||
expect(update._meta?.terminal_output).toEqual({
|
||||
terminal_id: "toolu_bash",
|
||||
data: "pushed ok",
|
||||
});
|
||||
expect(update._meta?.terminal_exit).toEqual({
|
||||
terminal_id: "toolu_bash",
|
||||
exit_code: 0,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("joins stderr after stdout like the code-execution path", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithHint, bashToolUse, false, {
|
||||
...structured,
|
||||
stderr: "warning: something",
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: "```console\npushed ok\nwarning: something\n```" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to raw text for backgrounded commands", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithHint, bashToolUse, true, {
|
||||
...structured,
|
||||
stdout: "",
|
||||
backgroundTaskId: "bash_1",
|
||||
});
|
||||
|
||||
expect(update._meta?.terminal_output?.data).toBe(`pushed ok${HINT}`);
|
||||
});
|
||||
|
||||
it("falls back to the raw content array for image output", () => {
|
||||
const imageResult: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_bash",
|
||||
content: [
|
||||
{ type: "image", source: { type: "base64", data: "aGk=", media_type: "image/png" } },
|
||||
],
|
||||
};
|
||||
const update = toolUpdateFromToolResult(imageResult, bashToolUse, true, {
|
||||
...structured,
|
||||
isImage: true,
|
||||
});
|
||||
|
||||
expect(update.content).toEqual([
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "image", data: "aGk=", mimeType: "image/png" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("re-establishes the abort notice and a failing exit code for interrupted commands", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithHint, bashToolUse, true, {
|
||||
...structured,
|
||||
stdout: "partial output",
|
||||
interrupted: true,
|
||||
});
|
||||
|
||||
expect(update._meta?.terminal_output).toEqual({
|
||||
terminal_id: "toolu_bash",
|
||||
data: "partial output\n[Command was aborted before completion]",
|
||||
});
|
||||
expect(update._meta?.terminal_exit).toEqual({
|
||||
terminal_id: "toolu_bash",
|
||||
exit_code: 1,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("re-establishes the truncation note and persisted path for too-large outputs", () => {
|
||||
const update = toolUpdateFromToolResult(rawWithHint, bashToolUse, true, {
|
||||
...structured,
|
||||
stdout: "clipped stdout",
|
||||
persistedOutputPath: "/tmp/tool-results/abc.txt",
|
||||
persistedOutputSize: 38100,
|
||||
});
|
||||
|
||||
expect(update._meta?.terminal_output).toEqual({
|
||||
terminal_id: "toolu_bash",
|
||||
data: "clipped stdout\n[Output truncated (38100 bytes total): full output saved to /tmp/tool-results/abc.txt]",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("WebSearch", () => {
|
||||
const searchToolUse = {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_search",
|
||||
name: "WebSearch",
|
||||
input: { query: "npm sigstore bug" },
|
||||
};
|
||||
|
||||
const rawDump: ToolResultBlockParam = {
|
||||
type: "tool_result",
|
||||
tool_use_id: "toolu_search",
|
||||
content:
|
||||
'Web search results for query: "npm sigstore bug"\n\nLinks: [{"title":"Issue #9722","url":"https://github.com/npm/cli/issues/9722"}]',
|
||||
};
|
||||
|
||||
it("renders hits as Title (url) lines from WebSearchOutput", () => {
|
||||
const update = toolUpdateFromToolResult(rawDump, searchToolUse, false, {
|
||||
query: "npm sigstore bug",
|
||||
durationSeconds: 5.5,
|
||||
results: [
|
||||
"I found one relevant issue:",
|
||||
{
|
||||
tool_use_id: "srvtoolu_1",
|
||||
content: [
|
||||
{ title: "Issue #9722", url: "https://github.com/npm/cli/issues/9722" },
|
||||
{ title: "sigstore-js", url: "https://github.com/sigstore/sigstore-js" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "I found one relevant issue:\nIssue #9722 (https://github.com/npm/cli/issues/9722)\nsigstore-js (https://github.com/sigstore/sigstore-js)",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the raw dump when tool_use_result is absent", () => {
|
||||
const update = toolUpdateFromToolResult(rawDump, searchToolUse, false);
|
||||
|
||||
expect((update.content?.[0] as any).content.text).toContain("Web search results for query");
|
||||
});
|
||||
|
||||
it("skips off-spec hits instead of rendering undefined fields", () => {
|
||||
const update = toolUpdateFromToolResult(rawDump, searchToolUse, false, {
|
||||
query: "npm sigstore bug",
|
||||
durationSeconds: 5.5,
|
||||
results: [
|
||||
{
|
||||
tool_use_id: "srvtoolu_1",
|
||||
content: [
|
||||
{ error_code: "provider_error" },
|
||||
{ title: "Issue #9722", url: "https://github.com/npm/cli/issues/9722" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(update).toEqual({
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: {
|
||||
type: "text",
|
||||
text: "Issue #9722 (https://github.com/npm/cli/issues/9722)",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the raw dump when every hit is off-spec", () => {
|
||||
const update = toolUpdateFromToolResult(rawDump, searchToolUse, false, {
|
||||
query: "npm sigstore bug",
|
||||
durationSeconds: 5.5,
|
||||
results: [{ tool_use_id: "srvtoolu_1", content: [{ error_code: "provider_error" }] }],
|
||||
});
|
||||
|
||||
expect((update.content?.[0] as any).content.text).toContain("Web search results for query");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
248
src/tools.ts
248
src/tools.ts
@@ -8,10 +8,13 @@ import {
|
||||
import { HookCallback } from "@anthropic-ai/claude-agent-sdk";
|
||||
import {
|
||||
AgentInput,
|
||||
AgentOutput,
|
||||
AskUserQuestionInput,
|
||||
BashInput,
|
||||
BashOutput,
|
||||
FileEditInput,
|
||||
FileReadInput,
|
||||
FileReadOutput,
|
||||
FileWriteInput,
|
||||
GlobInput,
|
||||
GrepInput,
|
||||
@@ -22,6 +25,7 @@ import {
|
||||
TodoWriteInput,
|
||||
WebFetchInput,
|
||||
WebSearchInput,
|
||||
WebSearchOutput,
|
||||
} from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
|
||||
import {
|
||||
ImageBlockParam,
|
||||
@@ -486,6 +490,55 @@ export function toolInfoFromToolUse(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrow the untyped message-level `tool_use_result` toward a per-tool Output
|
||||
* shape: rejects everything but a plain non-null object (arrays pass a bare
|
||||
* `typeof === "object"` check, so they're excluded here). The returned value
|
||||
* is only *nominally* typed — it arrives over the wire from arbitrary CLI
|
||||
* versions, so each caller must still guard the specific fields it reads
|
||||
* before trusting them.
|
||||
*/
|
||||
function structuredResult<T extends object>(toolUseResult: unknown): T | undefined {
|
||||
return toolUseResult !== null &&
|
||||
typeof toolUseResult === "object" &&
|
||||
!Array.isArray(toolUseResult)
|
||||
? (toolUseResult as T)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the model-directed trailer from a raw Agent/Task tool_result text:
|
||||
* a `<usage>…</usage>` totals block and/or an
|
||||
* `agentId: <id> (use SendMessage …)` continuation line at the end of the
|
||||
* text. Both patterns are tail-anchored and independent (older CLIs emit
|
||||
* variants with only one of them), so a format change makes them stop
|
||||
* matching rather than mangle the report.
|
||||
*/
|
||||
function stripAgentTrailer(text: string): string {
|
||||
return text
|
||||
.replace(/\n?<usage>[\s\S]*?<\/usage>\s*$/, "")
|
||||
.replace(/\n?agentId: [\w-]+ \([^)]*\)\s*$/, "");
|
||||
}
|
||||
|
||||
/** Apply {@link stripAgentTrailer} across a raw tool_result `content` (plain
|
||||
* string or block array), leaving non-text blocks untouched. */
|
||||
function stripAgentTrailerFromContent(content: unknown): unknown {
|
||||
if (typeof content === "string") {
|
||||
return stripAgentTrailer(content);
|
||||
}
|
||||
if (Array.isArray(content)) {
|
||||
return content.map((block) =>
|
||||
block !== null &&
|
||||
typeof block === "object" &&
|
||||
block.type === "text" &&
|
||||
typeof block.text === "string"
|
||||
? { ...block, text: stripAgentTrailer(block.text) }
|
||||
: block,
|
||||
);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
export function toolUpdateFromToolResult(
|
||||
toolResult:
|
||||
| ToolResultBlockParam
|
||||
@@ -500,6 +553,7 @@ export function toolUpdateFromToolResult(
|
||||
| BetaToolSearchToolResultBlockParam,
|
||||
toolUse: any | undefined,
|
||||
supportsTerminalOutput: boolean = false,
|
||||
toolUseResult?: unknown,
|
||||
): ToolUpdate {
|
||||
if (
|
||||
"is_error" in toolResult &&
|
||||
@@ -512,8 +566,63 @@ export function toolUpdateFromToolResult(
|
||||
return toAcpContentUpdate(toolResult.content, true);
|
||||
}
|
||||
|
||||
// Shared raw-text fallback: renders the tool_result content the model saw.
|
||||
// The structured cases below fall back to this when `tool_use_result` is
|
||||
// absent or fails its shape guard (older CLIs, replayed sessions).
|
||||
const rawContentUpdate = () =>
|
||||
toAcpContentUpdate(toolResult.content, "is_error" in toolResult ? toolResult.is_error : false);
|
||||
|
||||
switch (toolUse?.name) {
|
||||
case "Read":
|
||||
case "Read": {
|
||||
// The raw tool_result text is the model-facing view: line-numbered
|
||||
// content plus any appended <system-reminder> blocks (malicious-code
|
||||
// checks, memory staleness notes, …) that clients shouldn't see. The
|
||||
// structured FileReadOutput carries the clean content — rebuild the
|
||||
// line-numbered view from it. Non-text variants (image/notebook/pdf)
|
||||
// fall back to the raw content blocks, which already render fine.
|
||||
const structuredRead = structuredResult<FileReadOutput>(toolUseResult);
|
||||
if (
|
||||
structuredRead?.type === "text" &&
|
||||
typeof structuredRead.file?.content === "string" &&
|
||||
// An empty file has nothing to line-number; keep the raw view (the
|
||||
// model-facing "file is empty" note) rather than a phantom blank line.
|
||||
structuredRead.file.content.length > 0
|
||||
) {
|
||||
// startLine is typed non-optional but defended anyway; a Read's
|
||||
// `offset` input is the same 1-based starting line, so it beats a
|
||||
// blind 1 when an emitter omits the field.
|
||||
const startLine =
|
||||
structuredRead.file.startLine ??
|
||||
(toolUse.input as FileReadInput | undefined)?.offset ??
|
||||
1;
|
||||
// A trailing newline is a line terminator, not an extra line — don't
|
||||
// number a phantom empty line after it.
|
||||
let numbered = structuredRead.file.content
|
||||
.replace(/\n$/, "")
|
||||
.split("\n")
|
||||
.map((line, i) => `${startLine + i}\t${line}`)
|
||||
.join("\n");
|
||||
// The model-facing truncation banner doesn't survive reconstruction
|
||||
// from file.content (the SDK flag exists for exactly this case) —
|
||||
// re-establish it so a partial first page doesn't read as the whole
|
||||
// file.
|
||||
if (structuredRead.file.truncatedByTokenCap) {
|
||||
const { numLines, totalLines } = structuredRead.file;
|
||||
const detail =
|
||||
typeof numLines === "number" && typeof totalLines === "number"
|
||||
? `: showing ${numLines} of ${totalLines} lines`
|
||||
: "";
|
||||
numbered += `\n[File truncated${detail}]`;
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: markdownEscape(numbered) },
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
|
||||
return {
|
||||
content: toolResult.content.map((content: any) => ({
|
||||
@@ -541,6 +650,7 @@ export function toolUpdateFromToolResult(
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
case "Bash": {
|
||||
const result = toolResult.content;
|
||||
@@ -548,15 +658,57 @@ export function toolUpdateFromToolResult(
|
||||
const isError = "is_error" in toolResult && toolResult.is_error;
|
||||
|
||||
// Extract output and exit code from either format:
|
||||
// 1. BetaBashCodeExecutionResultBlock: { type: "bash_code_execution_result", stdout, stderr, return_code }
|
||||
// 2. Plain string content from a regular tool_result
|
||||
// 3. Array content (e.g. [{ type: "text", text: "..." }] for stdout,
|
||||
// 1. The structured BashOutput (message-level tool_use_result): its
|
||||
// stdout/stderr exclude the model-directed suffixes the raw text
|
||||
// carries (stale-read hints, gh rate-limit hints, the
|
||||
// persisted-output wrapper for too-large outputs — the interruption
|
||||
// and truncation facts those carried are re-established from the
|
||||
// structured flags below). Skipped for image output (the raw content
|
||||
// array carries the actual image blocks) and backgrounded commands
|
||||
// (the raw text carries the background-task notice; structured
|
||||
// stdout may be empty).
|
||||
// 2. BetaBashCodeExecutionResultBlock: { type: "bash_code_execution_result", stdout, stderr, return_code }
|
||||
// 3. Plain string content from a regular tool_result
|
||||
// 4. Array content (e.g. [{ type: "text", text: "..." }] for stdout,
|
||||
// or [{ type: "image", source: {...} }] when the local Bash tool
|
||||
// produces an image, e.g. piping a base64 data URI)
|
||||
let output = "";
|
||||
let exitCode = isError ? 1 : 0;
|
||||
|
||||
const structuredBash = structuredResult<BashOutput>(toolUseResult);
|
||||
if (
|
||||
structuredBash &&
|
||||
typeof structuredBash.stdout === "string" &&
|
||||
typeof structuredBash.stderr === "string" &&
|
||||
!structuredBash.isImage &&
|
||||
structuredBash.backgroundTaskId === undefined
|
||||
) {
|
||||
output = [structuredBash.stdout, structuredBash.stderr].filter(Boolean).join("\n");
|
||||
// Two raw-text notices don't survive the structured stdout/stderr —
|
||||
// re-establish them so the client isn't shown a clean-looking result:
|
||||
// the CLI appends its abort marker only to the model-facing text, and
|
||||
// an aborted command isn't a success, so synthesize a failing exit
|
||||
// code when the result wasn't already an error.
|
||||
if (structuredBash.interrupted) {
|
||||
output = [output, "[Command was aborted before completion]"].filter(Boolean).join("\n");
|
||||
exitCode = 1;
|
||||
}
|
||||
// Structured stdout is clipped (~30k chars) when the full output was
|
||||
// persisted to disk; without this note the clip is silent and the
|
||||
// path to the full output is lost.
|
||||
if (typeof structuredBash.persistedOutputPath === "string") {
|
||||
const size =
|
||||
typeof structuredBash.persistedOutputSize === "number"
|
||||
? ` (${structuredBash.persistedOutputSize} bytes total)`
|
||||
: "";
|
||||
output = [
|
||||
output,
|
||||
`[Output truncated${size}: full output saved to ${structuredBash.persistedOutputPath}]`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
} else if (
|
||||
result &&
|
||||
typeof result === "object" &&
|
||||
"type" in result &&
|
||||
@@ -619,6 +771,45 @@ export function toolUpdateFromToolResult(
|
||||
return {};
|
||||
}
|
||||
|
||||
case "Agent":
|
||||
case "Task": {
|
||||
// The raw tool_result text ends with a model-directed trailer (an
|
||||
// `agentId: … (use SendMessage …)` line plus a `<usage>` totals block)
|
||||
// that ACP clients shouldn't see. The message-level `tool_use_result`
|
||||
// carries the structured AgentOutput whose `content` is the subagent's
|
||||
// report without the trailer — render from it when present (per the SDK
|
||||
// 0.3.207 guidance) and fall back to the raw text otherwise (older CLIs,
|
||||
// replayed sessions).
|
||||
// Narrowed to the full union, not the completed variant — the status
|
||||
// check below is what discriminates it, and pre-narrowing would let
|
||||
// future field reads typecheck against a variant the runtime value may
|
||||
// not be.
|
||||
const structured = structuredResult<AgentOutput>(toolUseResult);
|
||||
if (
|
||||
structured?.status === "completed" &&
|
||||
Array.isArray(structured.content) &&
|
||||
// A completed subagent can end with zero text blocks; an empty
|
||||
// structured render would beat the raw fallback for no benefit.
|
||||
structured.content.length > 0
|
||||
) {
|
||||
return toAcpContentUpdate(
|
||||
structured.content,
|
||||
"is_error" in toolResult ? toolResult.is_error : false,
|
||||
);
|
||||
}
|
||||
// No structured report to render from (replayed sessions —
|
||||
// getSessionMessages doesn't expose the transcript's toolUseResult —
|
||||
// and older CLIs). The SDK advises rendering from tool_use_result
|
||||
// instead of parsing the text, but with no structured value the
|
||||
// tail-anchored strip is the only cleanup available; if the trailer
|
||||
// format changes it simply stops matching and the full raw text
|
||||
// renders, no worse than before.
|
||||
return toAcpContentUpdate(
|
||||
stripAgentTrailerFromContent(toolResult.content),
|
||||
"is_error" in toolResult ? toolResult.is_error : false,
|
||||
);
|
||||
}
|
||||
|
||||
case "Edit": // Edit is handled in hooks
|
||||
case "Write": {
|
||||
return {};
|
||||
@@ -628,15 +819,54 @@ export function toolUpdateFromToolResult(
|
||||
return { title: "Exited Plan Mode" };
|
||||
}
|
||||
|
||||
case "WebSearch": {
|
||||
// The raw tool_result text is a model-directed dump ("Web search
|
||||
// results for query: …\n\nLinks: [{…json…}]"). The structured
|
||||
// WebSearchOutput carries the hits — render them the way server-side
|
||||
// web_search_result blocks render ("Title (url)").
|
||||
const structuredSearch = structuredResult<WebSearchOutput>(toolUseResult);
|
||||
if (structuredSearch && Array.isArray(structuredSearch.results)) {
|
||||
const lines = structuredSearch.results.flatMap((entry) =>
|
||||
typeof entry === "string"
|
||||
? [entry]
|
||||
: Array.isArray(entry?.content)
|
||||
? // tool_use_result arrives untyped across CLI version skew —
|
||||
// skip off-spec hits rather than rendering
|
||||
// "undefined (undefined)" lines.
|
||||
entry.content.flatMap((hit) =>
|
||||
typeof hit?.title === "string" && typeof hit?.url === "string"
|
||||
? [formatWebSearchHit(hit)]
|
||||
: [],
|
||||
)
|
||||
: [],
|
||||
);
|
||||
if (lines.length > 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
content: { type: "text", text: lines.join("\n") },
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
return rawContentUpdate();
|
||||
}
|
||||
|
||||
default: {
|
||||
return toAcpContentUpdate(
|
||||
toolResult.content,
|
||||
"is_error" in toolResult ? toolResult.is_error : false,
|
||||
);
|
||||
return rawContentUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** One display format for a web-search hit, shared by the structured
|
||||
* WebSearchOutput render and the server-side `web_search_result` block so
|
||||
* the two paths can't drift. */
|
||||
function formatWebSearchHit(hit: { title: string; url: string }): string {
|
||||
return `${hit.title} (${hit.url})`;
|
||||
}
|
||||
|
||||
function toAcpContentUpdate(
|
||||
content: any,
|
||||
isError: boolean = false,
|
||||
@@ -711,7 +941,7 @@ function toAcpContentBlock(content: ToolResultContent, isError: boolean): Conten
|
||||
`Error: ${content.error_code}${content.error_message ? ` - ${content.error_message}` : ""}`,
|
||||
);
|
||||
case "web_search_result":
|
||||
return wrapText(`${content.title} (${content.url})`);
|
||||
return wrapText(formatWebSearchHit(content));
|
||||
case "web_search_tool_result_error":
|
||||
return wrapText(`Error: ${content.error_code}`);
|
||||
case "web_fetch_result":
|
||||
|
||||
Reference in New Issue
Block a user