Compare commits

...

20 Commits

Author SHA1 Message Date
acp-release-bot[bot]
0525e6dacb chore(main): release 0.45.0 (#772)
🤖 I have created a release *beep* *boop*
---


##
[0.45.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.44.0...v0.45.0)
(2026-06-15)


### Features

* **deps-dev:** bump the minor group with 3 updates
([#763](https://github.com/agentclientprotocol/claude-agent-acp/issues/763))
([7de5e4b](7de5e4bcca))
* **deps:** bump @anthropic-ai/claude-agent-sdk to 0.3.177
([#771](https://github.com/agentclientprotocol/claude-agent-acp/issues/771))
([1be5ca5](1be5ca57ee))


### Bug Fixes

* preserve ANTHROPIC_CUSTOM_MODEL_OPTION when availableModels is set
([#768](https://github.com/agentclientprotocol/claude-agent-acp/issues/768))
([cc2885f](cc2885f6a9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
2026-06-15 18:17:10 +02:00
Luca Dibattista
cc2885f6a9 fix: preserve ANTHROPIC_CUSTOM_MODEL_OPTION when availableModels is set (#768)
## Summary

When `availableModels` is set to a slim list (e.g.
`["sonnet","opus","haiku"]`), the model from
`ANTHROPIC_CUSTOM_MODEL_OPTION` was dropped from `session/new`'s model
picker. Claude Code's CLI keeps it; ACP hid it.

Per the [model-config
docs](https://code.claude.com/docs/en/model-config#add-a-custom-model-option),
`ANTHROPIC_CUSTOM_MODEL_OPTION` adds a custom entry "without replacing
the built-in aliases" and "appears at the bottom of the `/model` picker"
— i.e. it's additive, not subject to the alias allowlist.

Observed under:
* `availableModels: ["sonnet","opus","haiku"]`
* `ANTHROPIC_DEFAULT_OPUS_MODEL=claude-opus-4-6[1m]`
* `ANTHROPIC_CUSTOM_MODEL_OPTION=claude-opus-4-8[1m]`

| | Default | Sonnet | Opus | Haiku | Opus 4.8 |
|---|---|---|---|---|---|
| CLI picker | ✅ | ✅ | ✅ | ✅ | ✅ (bottom) |
| ACP (before) | ✅ | ✅ | ✅ | ✅ | ❌ |
| ACP (after) | ✅ | ✅ | ✅ | ✅ | ✅ (bottom) |

### Root cause

The SDK returns the custom option as an ordinary entry in
`initializationResult.models` with no flag distinguishing it
(`ModelInfo` has no `custom`/`isCustom` field).
`applyAvailableModelsAllowlist` treats `availableModels` as a strict
allowlist: keep `default`, then keep only SDK models matching an
allowlist entry. The custom value (`claude-opus-4-8[1m]`) matches no
alias, so it's filtered out — diverging from the CLI's additive
treatment.

### Fix

After the allowlist loop, append the model named by
`ANTHROPIC_CUSTOM_MODEL_OPTION` if the SDK surfaced it and it isn't
already present. It's placed last, matching the documented "bottom of
the picker" position, and exempted from the allowlist the same way we
already exempt `default` (the docs note the Default option is likewise
"not affected by `availableModels`").

The entry is preserved in the `ModelInfo[]` (`allowedModels` / session
`modelInfos`), not just the trimmed picker list, so its capability flags
(`supportedEffortLevels`, `supportsAutoMode`) flow to mode gating and
the effort dropdown when the custom model is selected.

Only the allowlist path needs this — when `availableModels` is absent,
ACP uses the full SDK list, which already contains the custom option.

## Test Plan

`src/tests/acp-agent-settings.test.ts` (TDD):
- Custom option preserved at the end of the list when absent from a slim
allowlist, with its name/description intact.
- No duplication when the custom option's exact ID is *also* in
`availableModels`.

No behavior change when `ANTHROPIC_CUSTOM_MODEL_OPTION` is unset or when
`availableModels` is absent.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:26:30 +00:00
dependabot[bot]
1be5ca57ee feat(deps): bump @anthropic-ai/claude-agent-sdk to 0.3.177 (#771)
Bumps the minor group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
|
[@agentclientprotocol/sdk](https://github.com/agentclientprotocol/typescript-sdk)
| `0.25.0` | `0.25.1` |
|
[@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript)
| `0.3.170` | `0.3.177` |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `25.9.2` | `25.9.3` |
| [eslint](https://github.com/eslint/eslint) | `10.4.1` | `10.5.0` |
|
[vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)
| `4.1.8` | `4.1.9` |
| [acorn](https://github.com/acornjs/acorn) | `8.16.0` | `8.17.0` |
| [body-parser](https://github.com/expressjs/body-parser) | `2.2.2` |
`2.3.0` |
| [obug](https://github.com/sxzz/obug) | `2.1.2` | `2.1.3` |


Updates `@agentclientprotocol/sdk` from 0.25.0 to 0.25.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/agentclientprotocol/typescript-sdk/releases">@​agentclientprotocol/sdk's
releases</a>.</em></p>
<blockquote>
<h2>v0.25.1</h2>
<h2><a
href="https://github.com/agentclientprotocol/typescript-sdk/compare/v0.25.0...v0.25.1">0.25.1</a>
(2026-06-13)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>cleanup zod generation (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/170">#170</a>)
(<a
href="be44483cf0">be44483</a>)</li>
<li>Zod type inference errors (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/177">#177</a>)
(<a
href="d9fafa2053">d9fafa2</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/agentclientprotocol/typescript-sdk/blob/main/CHANGELOG.md">@​agentclientprotocol/sdk's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/agentclientprotocol/typescript-sdk/compare/v0.25.0...v0.25.1">0.25.1</a>
(2026-06-13)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>cleanup zod generation (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/170">#170</a>)
(<a
href="be44483cf0">be44483</a>)</li>
<li>Zod type inference errors (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/177">#177</a>)
(<a
href="d9fafa2053">d9fafa2</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="cd8dc79b94"><code>cd8dc79</code></a>
chore(main): release 0.25.1 (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/171">#171</a>)</li>
<li><a
href="d9fafa2053"><code>d9fafa2</code></a>
fix: Zod type inference errors (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/177">#177</a>)</li>
<li><a
href="04c7fd91d3"><code>04c7fd9</code></a>
chore(deps): bump esbuild from 0.28.0 to 0.28.1 (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/176">#176</a>)</li>
<li><a
href="8d9de29709"><code>8d9de29</code></a>
chore(deps): bump actions/checkout from 6.0.2 to 6.0.3 (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/173">#173</a>)</li>
<li><a
href="8ca7903ce1"><code>8ca7903</code></a>
chore(deps): bump crate-ci/typos from 1.47.0 to 1.47.2 (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/174">#174</a>)</li>
<li><a
href="1c6c0b80f4"><code>1c6c0b8</code></a>
chore(deps-dev): bump <code>@​types/node</code> in the minor group (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/175">#175</a>)</li>
<li><a
href="be44483cf0"><code>be44483</code></a>
fix: cleanup zod generation (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/170">#170</a>)</li>
<li>See full diff in <a
href="https://github.com/agentclientprotocol/typescript-sdk/compare/v0.25.0...v0.25.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `@anthropic-ai/claude-agent-sdk` from 0.3.170 to 0.3.177
<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.177</h2>
<h2>What's changed</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.177</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.177
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.177
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.177
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.177
</code></pre>
<h2>v0.3.176</h2>
<h2>What's changed</h2>
<ul>
<li>Fixed turn <code>result</code> messages being dropped when multiple
turns complete while a background agent or workflow is running</li>
<li>Fixed background agent, remote agent, and MCP task state not being
restored when resuming a session via the SDK</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.176
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.176
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.176
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.176
</code></pre>
<h2>v0.3.175</h2>
<h2>What's changed</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.175</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.175
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.175
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.175
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.175
&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</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.177</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.177</li>
</ul>
<h2>0.3.176</h2>
<ul>
<li>Fixed turn <code>result</code> messages being dropped when multiple
turns complete while a background agent or workflow is running</li>
<li>Fixed background agent, remote agent, and MCP task state not being
restored when resuming a session via the SDK</li>
</ul>
<h2>0.3.175</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.175</li>
</ul>
<h2>0.3.174</h2>
<ul>
<li>SDK consumers now receive the <code>system/model_fallback</code>
message for all fallback triggers — <code>overloaded</code>,
<code>server_error</code>, and <code>last_resort</code> in addition to
<code>model_not_found</code> and <code>permission_denied</code> — and
the message's <code>trigger</code> field gained the
<code>server_error</code> and <code>last_resort</code> values</li>
</ul>
<h2>0.3.173</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.173</li>
</ul>
<h2>0.3.172</h2>
<ul>
<li>SDK <code>plugins</code> option now accepts <code>skipMcpDiscovery:
true</code> per plugin, so a host that manages a plugin's MCP
connections itself can load skills/hooks from the plugin path without
the engine re-reading its <code>.mcp.json</code></li>
<li>Fixed slash-followed-by-whitespace input (e.g. <code>/ add
tests</code>) being silently dropped instead of treated as a plain
prompt</li>
</ul>
<h2>0.3.171</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.171</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="12be9e52fd"><code>12be9e5</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="4bbff4fbf2"><code>4bbff4f</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="e05f10c790"><code>e05f10c</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="8ef4c3fe8e"><code>8ef4c3f</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="944ab13cf2"><code>944ab13</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="2cfb79cb35"><code>2cfb79c</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.170...v0.3.177">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/node` from 25.9.2 to 25.9.3
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />

Updates `eslint` from 10.4.1 to 10.5.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.5.0</h2>
<h2>Features</h2>
<ul>
<li><a
href="5ca8c5278e"><code>5ca8c52</code></a>
feat: correct stack tracking in max-nested-callbacks (<a
href="https://redirect.github.com/eslint/eslint/issues/20973">#20973</a>)
(Pixel998)</li>
<li><a
href="b565783760"><code>b565783</code></a>
feat: report no-with violations at the with keyword (<a
href="https://redirect.github.com/eslint/eslint/issues/20971">#20971</a>)
(Pixel998)</li>
<li><a
href="2ce032fbc7"><code>2ce032f</code></a>
feat: report max-lines-per-function violations at function head (<a
href="https://redirect.github.com/eslint/eslint/issues/20966">#20966</a>)
(Pixel998)</li>
<li><a
href="732cb3e09d"><code>732cb3e</code></a>
feat: report max-nested-callbacks violations at function head (<a
href="https://redirect.github.com/eslint/eslint/issues/20967">#20967</a>)
(Pixel998)</li>
<li><a
href="f9c138a0ba"><code>f9c138a</code></a>
feat: report max-depth violations on keywords (<a
href="https://redirect.github.com/eslint/eslint/issues/20943">#20943</a>)
(Pixel998)</li>
<li><a
href="bdb496cc0d"><code>bdb496c</code></a>
feat: correct max-depth handling for else-if chains (<a
href="https://redirect.github.com/eslint/eslint/issues/20944">#20944</a>)
(Pixel998)</li>
<li><a
href="c29687354a"><code>c296873</code></a>
feat: update error loc in <code>max-statements</code> to function header
(<a
href="https://redirect.github.com/eslint/eslint/issues/20907">#20907</a>)
(Taejin Kim)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="8ae1b5b856"><code>8ae1b5b</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="ca7eb90127"><code>ca7eb90</code></a>
docs: update Node.js prerequisites to include ICU support (<a
href="https://redirect.github.com/eslint/eslint/issues/20962">#20962</a>)
(Francesco Trotta)</li>
<li><a
href="f99b47a679"><code>f99b47a</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="acf03d4eed"><code>acf03d4</code></a>
docs: clarify precedence of parserOptions over languageOptions (<a
href="https://redirect.github.com/eslint/eslint/issues/20926">#20926</a>)
(sethamus)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="b18bf58c5a"><code>b18bf58</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/20959">#20959</a>)
(ESLint Bot)</li>
<li><a
href="c2d1444df7"><code>c2d1444</code></a>
refactor: replace areAllSegmentsUnreachable with !isAnySegmentReachable
(<a
href="https://redirect.github.com/eslint/eslint/issues/20951">#20951</a>)
(Taejin Kim)</li>
<li><a
href="243b8c5601"><code>243b8c5</code></a>
chore: enhance config-rule to support oneOf, anyOf, and nested schemas
(<a
href="https://redirect.github.com/eslint/eslint/issues/20788">#20788</a>)
(kuldeep kumar)</li>
<li><a
href="217b2a91f4"><code>217b2a9</code></a>
test: add unit tests for ParserService (<a
href="https://redirect.github.com/eslint/eslint/issues/20949">#20949</a>)
(Taejin Kim)</li>
<li><a
href="72003e781d"><code>72003e7</code></a>
test: add location information to error messages in
<code>max-statements</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20945">#20945</a>)
(lumir)</li>
<li><a
href="7797c26697"><code>7797c26</code></a>
refactor: deduplicate isAnySegmentReachable across rules (<a
href="https://redirect.github.com/eslint/eslint/issues/20890">#20890</a>)
(Taejin Kim)</li>
<li><a
href="67c46fa6e4"><code>67c46fa</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/20938">#20938</a>)
(ESLint Bot)</li>
<li><a
href="95d8c7a99f"><code>95d8c7a</code></a>
chore: update dependency <code>@​eslint/json</code> to v2 (<a
href="https://redirect.github.com/eslint/eslint/issues/20934">#20934</a>)
(renovate[bot])</li>
<li><a
href="cf9e496205"><code>cf9e496</code></a>
chore: update <code>@​arethetypeswrong/cli</code> to 0.18.3 (<a
href="https://redirect.github.com/eslint/eslint/issues/20933">#20933</a>)
(Pixel998)</li>
<li><a
href="fb6d3960ca"><code>fb6d396</code></a>
test: run type tests with TypeScript 7 (<a
href="https://redirect.github.com/eslint/eslint/issues/20868">#20868</a>)
(sethamus)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="de3b672a26"><code>de3b672</code></a>
10.5.0</li>
<li><a
href="362a518513"><code>362a518</code></a>
Build: changelog update for 10.5.0</li>
<li><a
href="5ca8c5278e"><code>5ca8c52</code></a>
feat: correct stack tracking in max-nested-callbacks (<a
href="https://redirect.github.com/eslint/eslint/issues/20973">#20973</a>)</li>
<li><a
href="b565783760"><code>b565783</code></a>
feat: report no-with violations at the with keyword (<a
href="https://redirect.github.com/eslint/eslint/issues/20971">#20971</a>)</li>
<li><a
href="2ce032fbc7"><code>2ce032f</code></a>
feat: report max-lines-per-function violations at function head (<a
href="https://redirect.github.com/eslint/eslint/issues/20966">#20966</a>)</li>
<li><a
href="732cb3e09d"><code>732cb3e</code></a>
feat: report max-nested-callbacks violations at function head (<a
href="https://redirect.github.com/eslint/eslint/issues/20967">#20967</a>)</li>
<li><a
href="f9c138a0ba"><code>f9c138a</code></a>
feat: report max-depth violations on keywords (<a
href="https://redirect.github.com/eslint/eslint/issues/20943">#20943</a>)</li>
<li><a
href="8ae1b5b856"><code>8ae1b5b</code></a>
docs: Update README</li>
<li><a
href="ca7eb90127"><code>ca7eb90</code></a>
docs: update Node.js prerequisites to include ICU support (<a
href="https://redirect.github.com/eslint/eslint/issues/20962">#20962</a>)</li>
<li><a
href="b18bf58c5a"><code>b18bf58</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/20959">#20959</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/eslint/eslint/compare/v10.4.1...v10.5.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `vitest` from 4.1.8 to 4.1.9
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/HEAD/packages/vitest">compare
view</a></li>
</ul>
</details>
<br />

Updates `@anthropic-ai/claude-agent-sdk-darwin-arm64` from 0.3.170 to
0.3.177

Updates `@anthropic-ai/claude-agent-sdk-darwin-x64` from 0.3.170 to
0.3.177

Updates `@anthropic-ai/claude-agent-sdk-linux-arm64` from 0.3.170 to
0.3.177

Updates `@anthropic-ai/claude-agent-sdk-linux-arm64-musl` from 0.3.170
to 0.3.177

Updates `@anthropic-ai/claude-agent-sdk-linux-x64` from 0.3.170 to
0.3.177

Updates `@anthropic-ai/claude-agent-sdk-linux-x64-musl` from 0.3.170 to
0.3.177

Updates `@anthropic-ai/claude-agent-sdk-win32-arm64` from 0.3.170 to
0.3.177

Updates `@anthropic-ai/claude-agent-sdk-win32-x64` from 0.3.170 to
0.3.177

Updates `@vitest/expect` from 4.1.8 to 4.1.9
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/HEAD/packages/expect">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitest/mocker` from 4.1.8 to 4.1.9
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/HEAD/packages/mocker">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitest/pretty-format` from 4.1.8 to 4.1.9
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/HEAD/packages/pretty-format">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitest/runner` from 4.1.8 to 4.1.9
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/HEAD/packages/runner">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitest/snapshot` from 4.1.8 to 4.1.9
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/HEAD/packages/snapshot">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitest/spy` from 4.1.8 to 4.1.9
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/HEAD/packages/spy">compare
view</a></li>
</ul>
</details>
<br />

Updates `@vitest/utils` from 4.1.8 to 4.1.9
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/HEAD/packages/utils">compare
view</a></li>
</ul>
</details>
<br />

Updates `acorn` from 8.16.0 to 8.17.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5b9d16c256"><code>5b9d16c</code></a>
Mark version 8.17.0</li>
<li><a
href="449f9fafb8"><code>449f9fa</code></a>
Make linter happy</li>
<li><a
href="9e1bced50d"><code>9e1bced</code></a>
Add a strict option to turn on strict mode for scripts</li>
<li><a
href="8a47812670"><code>8a47812</code></a>
Convert stack overflow errors to SyntaxError instances</li>
<li><a
href="3bc83f91bb"><code>3bc83f9</code></a>
Update maintainer email</li>
<li><a
href="3f40dbe684"><code>3f40dbe</code></a>
Bump test262</li>
<li><a
href="8bd029044e"><code>8bd0290</code></a>
Improve handling of using/await using in for loops</li>
<li><a
href="f25fdeda46"><code>f25fded</code></a>
Upgrade some dev dependencies</li>
<li><a
href="27781e4542"><code>27781e4</code></a>
Remove unnecessary test</li>
<li><a
href="2dded4b9de"><code>2dded4b</code></a>
Forbid 'new super()'</li>
<li>Additional commits viewable in <a
href="https://github.com/acornjs/acorn/compare/8.16.0...8.17.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `body-parser` from 2.2.2 to 2.3.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/expressjs/body-parser/releases">body-parser's
releases</a>.</em></p>
<blockquote>
<h2>v2.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>build(deps): bump actions/download-artifact from 6.0.0 to 7.0.0 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/681">expressjs/body-parser#681</a></li>
<li>build(deps): bump actions/checkout from 5.0.0 to 6.0.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/682">expressjs/body-parser#682</a></li>
<li>build(deps): bump actions/setup-node from 6.0.0 to 6.1.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/683">expressjs/body-parser#683</a></li>
<li>build(deps): bump actions/upload-artifact from 5.0.0 to 6.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/685">expressjs/body-parser#685</a></li>
<li>build(deps): bump github/codeql-action from 4.31.2 to 4.31.9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/684">expressjs/body-parser#684</a></li>
<li>perf(urlencoded): move empty-body guard to avoid extra function
closure by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/647">expressjs/body-parser#647</a></li>
<li>Improve ESM compatibility by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/697">expressjs/body-parser#697</a></li>
<li>docs: add recommendations for configuring payload limits by <a
href="https://github.com/bjohansebas"><code>@​bjohansebas</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/699">expressjs/body-parser#699</a></li>
<li>build(deps): bump actions/setup-node from 6.1.0 to 6.2.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/701">expressjs/body-parser#701</a></li>
<li>build(deps): bump github/codeql-action from 4.31.10 to 4.32.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/702">expressjs/body-parser#702</a></li>
<li>build(deps): bump actions/checkout from 6.0.1 to 6.0.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/700">expressjs/body-parser#700</a></li>
<li>chore: add explicit type commonjs to package.json by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/711">expressjs/body-parser#711</a></li>
<li>deps: update dependencies to latest versions by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/708">expressjs/body-parser#708</a></li>
<li>build(deps): bump actions/download-artifact from 7.0.0 to 8.0.0 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/712">expressjs/body-parser#712</a></li>
<li>build(deps): bump github/codeql-action from 4.32.0 to 4.32.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/713">expressjs/body-parser#713</a></li>
<li>build(deps): bump actions/upload-artifact from 6.0.0 to 7.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/714">expressjs/body-parser#714</a></li>
<li>fix: improve limit option validation by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/698">expressjs/body-parser#698</a></li>
<li>build(deps): bump github/codeql-action from 4.32.4 to 4.35.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/719">expressjs/body-parser#719</a></li>
<li>build(deps): bump actions/setup-node from 6.2.0 to 6.3.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/718">expressjs/body-parser#718</a></li>
<li>build(deps): bump actions/download-artifact from 8.0.0 to 8.0.1 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/717">expressjs/body-parser#717</a></li>
<li>perf: eliminate conditional check in json strict mode hot path by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/651">expressjs/body-parser#651</a></li>
<li>ci: add node.js 26 to text matrix by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/726">expressjs/body-parser#726</a></li>
<li>build(deps): bump actions/setup-node from 6.3.0 to 6.4.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/725">expressjs/body-parser#725</a></li>
<li>build(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/724">expressjs/body-parser#724</a></li>
<li>build(deps): bump github/codeql-action from 4.35.1 to 4.35.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/723">expressjs/body-parser#723</a></li>
<li>Upgrade &quot;content-type&quot; by <a
href="https://github.com/blakeembrey"><code>@​blakeembrey</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/728">expressjs/body-parser#728</a></li>
<li>refactor: switch to const/let and enable eslint no-var rule by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/729">expressjs/body-parser#729</a></li>
<li>Update outdated reference to MDN docs by <a
href="https://github.com/krzysdz"><code>@​krzysdz</code></a> in <a
href="https://redirect.github.com/expressjs/body-parser/pull/730">expressjs/body-parser#730</a></li>
<li>build(deps): bump github/codeql-action from 4.35.3 to 4.36.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/731">expressjs/body-parser#731</a></li>
<li>build(deps): bump actions/checkout from 6.0.2 to 6.0.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/732">expressjs/body-parser#732</a></li>
<li>chore: updated deps to latest by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/733">expressjs/body-parser#733</a></li>
<li>2.3.0 by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/735">expressjs/body-parser#735</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/krzysdz"><code>@​krzysdz</code></a> made
their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/730">expressjs/body-parser#730</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0">https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/expressjs/body-parser/blob/master/HISTORY.md">body-parser's
changelog</a>.</em></p>
<blockquote>
<h1>2.3.0 / 2026-06-15</h1>
<ul>
<li>fix: use static exports instead of lazy getters to improve ESM
compatibility</li>
<li>feat: add subpath exports for individual parsers</li>
<li>fix: improve <code>limit</code> option validation (<a
href="https://redirect.github.com/expressjs/body-parser/issues/698">#698</a>)
<ul>
<li>Invalid <code>limit</code> values (e.g. unparseable strings or
<code>NaN</code>) now throw instead of being silently ignored, which
previously disabled size limit enforcement</li>
<li><code>null</code> and <code>undefined</code> fall back to the
default 100kb limit</li>
</ul>
</li>
<li>deps:
<ul>
<li>content-type@^2.0.0</li>
<li>http-errors@^2.0.1</li>
<li>iconv-lite^0.7.2</li>
<li>qs@^6.15.2</li>
<li>raw-body@^3.0.2</li>
<li>type-is@^2.1.0</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d0f2ace6c7"><code>d0f2ace</code></a>
2.3.0 (<a
href="https://redirect.github.com/expressjs/body-parser/issues/735">#735</a>)</li>
<li><a
href="7d03f2f9d5"><code>7d03f2f</code></a>
chore: updated deps to latest (<a
href="https://redirect.github.com/expressjs/body-parser/issues/733">#733</a>)</li>
<li><a
href="8024ba7a81"><code>8024ba7</code></a>
build(deps): bump actions/checkout from 6.0.2 to 6.0.3 (<a
href="https://redirect.github.com/expressjs/body-parser/issues/732">#732</a>)</li>
<li><a
href="32b4ed4639"><code>32b4ed4</code></a>
build(deps): bump github/codeql-action from 4.35.3 to 4.36.1 (<a
href="https://redirect.github.com/expressjs/body-parser/issues/731">#731</a>)</li>
<li><a
href="ff0f6b9071"><code>ff0f6b9</code></a>
docs: update outdated reference to MDN docs (<a
href="https://redirect.github.com/expressjs/body-parser/issues/730">#730</a>)</li>
<li><a
href="14d001a9c9"><code>14d001a</code></a>
refactor: switch to const/let and enable eslint no-var rule (<a
href="https://redirect.github.com/expressjs/body-parser/issues/729">#729</a>)</li>
<li><a
href="37f36a2752"><code>37f36a2</code></a>
deps: update content-type and type-is (<a
href="https://redirect.github.com/expressjs/body-parser/issues/728">#728</a>)</li>
<li><a
href="e1c244bf55"><code>e1c244b</code></a>
build(deps): bump github/codeql-action from 4.35.1 to 4.35.3 (<a
href="https://redirect.github.com/expressjs/body-parser/issues/723">#723</a>)</li>
<li><a
href="e01087f521"><code>e01087f</code></a>
build(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 (<a
href="https://redirect.github.com/expressjs/body-parser/issues/724">#724</a>)</li>
<li><a
href="a7698d3028"><code>a7698d3</code></a>
build(deps): bump actions/setup-node from 6.3.0 to 6.4.0 (<a
href="https://redirect.github.com/expressjs/body-parser/issues/725">#725</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `obug` from 2.1.2 to 2.1.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sxzz/obug/releases">obug's
releases</a>.</em></p>
<blockquote>
<h2>v2.1.3</h2>
<h3>   🚀 Features</h3>
<ul>
<li>Add <code>sideEffects</code> field  -  by <a
href="https://github.com/sxzz"><code>@​sxzz</code></a> <a
href="https://github.com/sxzz/obug/commit/014584c"><!-- raw HTML omitted
-->(01458)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/sxzz/obug/compare/v2.1.2...v2.1.3">View changes
on GitHub</a></h5>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="6508a03a1e"><code>6508a03</code></a>
chore: release v2.1.3</li>
<li><a
href="014584c3c8"><code>014584c</code></a>
feat: add <code>sideEffects</code> field</li>
<li>See full diff in <a
href="https://github.com/sxzz/obug/compare/v2.1.2...v2.1.3">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>
2026-06-15 17:21:31 +02:00
dependabot[bot]
7de5e4bcca feat(deps-dev): bump the minor group with 3 updates (#763)
Bumps the minor group with 3 updates:
[@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript),
[@napi-rs/wasm-runtime](https://github.com/napi-rs/napi-rs/tree/HEAD/wasm-runtime)
and [semver](https://github.com/npm/node-semver).

Updates `@anthropic-ai/sdk` from 0.103.0 to 0.104.1
<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.104.1</h2>
<h2>0.104.1 (2026-06-09)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.104.0...sdk-v0.104.1">sdk-v0.104.0...sdk-v0.104.1</a></p>
<h3>Bug Fixes</h3>
<ul>
<li><strong>api:</strong> add <code>frontier_llm</code> refusal category
(<a
href="465e6866d6">465e686</a>)</li>
</ul>
<h2>sdk: v0.104.0</h2>
<h2>0.104.0 (2026-06-09)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.103.0...sdk-v0.104.0">sdk-v0.103.0...sdk-v0.104.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> add support for Managed Agents deployments and
environment variable credentials (<a
href="d01e38b5b6">d01e38b</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.104.1 (2026-06-09)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.104.0...sdk-v0.104.1">sdk-v0.104.0...sdk-v0.104.1</a></p>
<h3>Bug Fixes</h3>
<ul>
<li><strong>api:</strong> add <code>frontier_llm</code> refusal category
(<a
href="465e6866d6">465e686</a>)</li>
</ul>
<h2>0.104.0 (2026-06-09)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.103.0...sdk-v0.104.0">sdk-v0.103.0...sdk-v0.104.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> add support for Managed Agents deployments and
environment variable credentials (<a
href="d01e38b5b6">d01e38b</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9a0442d88e"><code>9a0442d</code></a>
chore: release main</li>
<li><a
href="1ccd4012e7"><code>1ccd401</code></a>
fix(api): add <code>frontier_llm</code> refusal category</li>
<li><a
href="8be0232e92"><code>8be0232</code></a>
chore: release main</li>
<li><a
href="813af32183"><code>813af32</code></a>
feat(api): add support for Managed Agents deployments and environment
variabl...</li>
<li>See full diff in <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.103.0...sdk-v0.104.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `@napi-rs/wasm-runtime` from 1.1.4 to 1.1.5
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/napi-rs/napi-rs/blob/main/wasm-runtime/CHANGELOG.md">@​napi-rs/wasm-runtime's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/napi-rs/napi-rs/compare/@napi-rs/wasm-runtime@1.1.4...@napi-rs/wasm-runtime@1.1.5">1.1.5</a>
(2026-06-10)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>wasm-runtime:</strong> preserve Buffer return values in
fs-proxy (<a
href="https://redirect.github.com/napi-rs/napi-rs/issues/3250">#3250</a>)
(<a
href="c70f205a07">c70f205</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="670e5d3195"><code>670e5d3</code></a>
chore(release): publish</li>
<li><a
href="ad7b1c8fbf"><code>ad7b1c8</code></a>
chore(deps): lock file maintenance (<a
href="https://github.com/napi-rs/napi-rs/tree/HEAD/wasm-runtime/issues/3318">#3318</a>)</li>
<li><a
href="c70f205a07"><code>c70f205</code></a>
fix(wasm-runtime): preserve Buffer return values in fs-proxy (<a
href="https://github.com/napi-rs/napi-rs/tree/HEAD/wasm-runtime/issues/3250">#3250</a>)</li>
<li>See full diff in <a
href="https://github.com/napi-rs/napi-rs/commits/@napi-rs/wasm-runtime@1.1.5/wasm-runtime">compare
view</a></li>
</ul>
</details>
<br />

Updates `semver` from 7.8.3 to 7.8.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/npm/node-semver/releases">semver's
releases</a>.</em></p>
<blockquote>
<h2>v7.8.4</h2>
<h2><a
href="https://github.com/npm/node-semver/compare/v7.8.3...v7.8.4">7.8.4</a>
(2026-06-09)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><a
href="e583226b16"><code>e583226</code></a>
<a href="https://redirect.github.com/npm/node-semver/pull/874">#874</a>
reject numeric segments after x-ranges (<a
href="https://github.com/pupuking723"><code>@​pupuking723</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/npm/node-semver/blob/main/CHANGELOG.md">semver's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/npm/node-semver/compare/v7.8.3...v7.8.4">7.8.4</a>
(2026-06-09)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><a
href="e583226b16"><code>e583226</code></a>
<a href="https://redirect.github.com/npm/node-semver/pull/874">#874</a>
reject numeric segments after x-ranges (<a
href="https://github.com/pupuking723"><code>@​pupuking723</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8640bd68f1"><code>8640bd6</code></a>
chore: release 7.8.4 (<a
href="https://redirect.github.com/npm/node-semver/issues/875">#875</a>)</li>
<li><a
href="e583226b16"><code>e583226</code></a>
fix: reject numeric segments after x-ranges</li>
<li>See full diff in <a
href="https://github.com/npm/node-semver/compare/v7.8.3...v7.8.4">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>
2026-06-10 17:21:39 +02:00
acp-release-bot[bot]
85a45b2895 chore(main): release 0.44.0 (#759)
🤖 I have created a release *beep* *boop*
---


##
[0.44.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.43.0...v0.44.0)
(2026-06-09)


### Features

* **deps:** bump the minor group with 14 updates
([#758](https://github.com/agentclientprotocol/claude-agent-acp/issues/758))
([7a70162](7a701623df))
* **deps:** update to @anthropic-ai/claude-agent-sdk 0.3.170
([#761](https://github.com/agentclientprotocol/claude-agent-acp/issues/761))
([d8af943](d8af943a1e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
2026-06-09 19:45:19 +02:00
Anna Zhdan
d8af943a1e feat(deps): update to @anthropic-ai/claude-agent-sdk 0.3.170 (#761)
## Summary
- Bump `@anthropic-ai/claude-agent-sdk` from 0.3.169 to **0.3.170**
- Bump `@anthropic-ai/sdk` from 0.102.0 to **0.103.0**
- Handle two new SDK types: `model_refusal_fallback` (system message)
and `fallback` (content block)

## Test plan
- [x] Build passes
- [x] All 355 tests pass (15 skipped)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-09 19:41:17 +02:00
dependabot[bot]
7a701623df feat(deps): bump the minor group with 14 updates (#758)
Bumps the minor group with 14 updates:

| Package | From | To |
| --- | --- | --- |
|
[@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin)
| `8.60.1` | `8.61.0` |
|
[@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser)
| `8.60.1` | `8.61.0` |
| [prettier](https://github.com/prettier/prettier) | `3.8.3` | `3.8.4` |
|
[@typescript-eslint/project-service](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/project-service)
| `8.60.1` | `8.61.0` |
|
[@typescript-eslint/scope-manager](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/scope-manager)
| `8.60.1` | `8.61.0` |
|
[@typescript-eslint/tsconfig-utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/tsconfig-utils)
| `8.60.1` | `8.61.0` |
|
[@typescript-eslint/type-utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/type-utils)
| `8.60.1` | `8.61.0` |
|
[@typescript-eslint/types](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/types)
| `8.60.1` | `8.61.0` |
|
[@typescript-eslint/typescript-estree](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-estree)
| `8.60.1` | `8.61.0` |
|
[@typescript-eslint/utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/utils)
| `8.60.1` | `8.61.0` |
|
[@typescript-eslint/visitor-keys](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/visitor-keys)
| `8.60.1` | `8.61.0` |
| [hono](https://github.com/honojs/hono) | `4.12.24` | `4.12.25` |
| [semver](https://github.com/npm/node-semver) | `7.8.2` | `7.8.3` |
| [side-channel](https://github.com/ljharb/side-channel) | `1.1.0` |
`1.1.1` |

Updates `@typescript-eslint/eslint-plugin` from 8.60.1 to 8.61.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/eslint-plugin's
releases</a>.</em></p>
<blockquote>
<h2>v8.61.0</h2>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
<li><strong>ast-spec:</strong> tighten types of
<code>ArrowFunction</code>, <code>YieldExpression</code>,
<code>TSTypePredicate</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12373">#12373</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>rule-schema-to-typescript-types:</strong> respect ECMAScript
line terminators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12374">#12374</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md">@​typescript-eslint/eslint-plugin's
changelog</a>.</em></p>
<blockquote>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16a5b247af"><code>16a5b24</code></a>
chore(release): publish 8.61.0</li>
<li><a
href="ef1fd28c68"><code>ef1fd28</code></a>
feat(ast-spec): change type of <code>UnaryExpression.prefix</code> to
always <code>true</code> (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin/issues/12">#12</a>...</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.0/packages/eslint-plugin">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/parser` from 8.60.1 to 8.61.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/parser's
releases</a>.</em></p>
<blockquote>
<h2>v8.61.0</h2>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
<li><strong>ast-spec:</strong> tighten types of
<code>ArrowFunction</code>, <code>YieldExpression</code>,
<code>TSTypePredicate</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12373">#12373</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>rule-schema-to-typescript-types:</strong> respect ECMAScript
line terminators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12374">#12374</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md">@​typescript-eslint/parser's
changelog</a>.</em></p>
<blockquote>
<h2>8.61.0 (2026-06-08)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16a5b247af"><code>16a5b24</code></a>
chore(release): publish 8.61.0</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.0/packages/parser">compare
view</a></li>
</ul>
</details>
<br />

Updates `prettier` from 3.8.3 to 3.8.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/prettier/releases">prettier's
releases</a>.</em></p>
<blockquote>
<h2>3.8.4</h2>
<ul>
<li>Markdown: Fix blank lines between list items and nested sub-lists
being removed in Markdown/MDX (<a
href="https://redirect.github.com/prettier/prettier/pull/17746">prettier/prettier#17746</a>
by <a
href="https://github.com/byplayer"><code>@​byplayer</code></a>)</li>
</ul>
<p>🔗 <a
href="https://github.com/prettier/prettier/blob/3.8.4/CHANGELOG.md#384">Changelog</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md">prettier's
changelog</a>.</em></p>
<blockquote>
<h1>3.8.4</h1>
<p><a
href="https://github.com/prettier/prettier/compare/3.8.3...3.8.4">diff</a></p>
<h4>Markdown: Fix blank lines between list items and nested sub-lists
being removed in Markdown/MDX (<a
href="https://redirect.github.com/prettier/prettier/pull/17746">#17746</a>
by <a
href="https://github.com/byplayer"><code>@​byplayer</code></a>)</h4>
<p>Prettier was removing blank lines between list items and their nested
sub-lists, converting loose lists into tight lists and changing their
semantic meaning.</p>
<!-- raw HTML omitted -->
<pre lang="markdown"><code>&lt;!-- Input --&gt;
- a
<ul>
<li>
<p>b</p>
</li>
<li>
<p>c</p>
<ul>
<li>d</li>
</ul>
</li>
</ul>
<p>&lt;!-- Prettier 3.8.3 --&gt;</p>
<ul>
<li>a
<ul>
<li>b</li>
</ul>
</li>
<li>c
<ul>
<li>d</li>
</ul>
</li>
</ul>
<p>&lt;!-- Prettier 3.8.4 --&gt;</p>
<ul>
<li>
<p>a</p>
<ul>
<li>b</li>
</ul>
</li>
<li>
<p>c</p>
<ul>
<li>d<br />
</code></pre></li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="1c6ba55391"><code>1c6ba55</code></a>
Release 3.8.4</li>
<li><a
href="4a673dc9b5"><code>4a673dc</code></a>
Fix blank lines between list items and nested sub-lists being removed in
Mark...</li>
<li><a
href="074aaedbb0"><code>074aaed</code></a>
Replace <code>main</code> branch in changelog link with tags (<a
href="https://redirect.github.com/prettier/prettier/issues/19054">#19054</a>)</li>
<li><a
href="c22a003ae9"><code>c22a003</code></a>
Bump Prettier dependency to 3.8.3</li>
<li><a
href="07bad1f045"><code>07bad1f</code></a>
Clean changelog_unreleased</li>
<li>See full diff in <a
href="https://github.com/prettier/prettier/compare/3.8.3...3.8.4">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/project-service` from 8.60.1 to 8.61.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/project-service's
releases</a>.</em></p>
<blockquote>
<h2>v8.61.0</h2>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
<li><strong>ast-spec:</strong> tighten types of
<code>ArrowFunction</code>, <code>YieldExpression</code>,
<code>TSTypePredicate</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12373">#12373</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>rule-schema-to-typescript-types:</strong> respect ECMAScript
line terminators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12374">#12374</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/project-service/CHANGELOG.md">@​typescript-eslint/project-service's
changelog</a>.</em></p>
<blockquote>
<h2>8.61.0 (2026-06-08)</h2>
<p>This was a version bump only for project-service to align it with
other projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16a5b247af"><code>16a5b24</code></a>
chore(release): publish 8.61.0</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.0/packages/project-service">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/scope-manager` from 8.60.1 to 8.61.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/scope-manager's
releases</a>.</em></p>
<blockquote>
<h2>v8.61.0</h2>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
<li><strong>ast-spec:</strong> tighten types of
<code>ArrowFunction</code>, <code>YieldExpression</code>,
<code>TSTypePredicate</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12373">#12373</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>rule-schema-to-typescript-types:</strong> respect ECMAScript
line terminators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12374">#12374</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/scope-manager/CHANGELOG.md">@​typescript-eslint/scope-manager's
changelog</a>.</em></p>
<blockquote>
<h2>8.61.0 (2026-06-08)</h2>
<p>This was a version bump only for scope-manager to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16a5b247af"><code>16a5b24</code></a>
chore(release): publish 8.61.0</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.0/packages/scope-manager">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/tsconfig-utils` from 8.60.1 to 8.61.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/tsconfig-utils's
releases</a>.</em></p>
<blockquote>
<h2>v8.61.0</h2>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
<li><strong>ast-spec:</strong> tighten types of
<code>ArrowFunction</code>, <code>YieldExpression</code>,
<code>TSTypePredicate</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12373">#12373</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>rule-schema-to-typescript-types:</strong> respect ECMAScript
line terminators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12374">#12374</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/tsconfig-utils/CHANGELOG.md">@​typescript-eslint/tsconfig-utils's
changelog</a>.</em></p>
<blockquote>
<h2>8.61.0 (2026-06-08)</h2>
<p>This was a version bump only for tsconfig-utils to align it with
other projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16a5b247af"><code>16a5b24</code></a>
chore(release): publish 8.61.0</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.0/packages/tsconfig-utils">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/type-utils` from 8.60.1 to 8.61.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/type-utils's
releases</a>.</em></p>
<blockquote>
<h2>v8.61.0</h2>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
<li><strong>ast-spec:</strong> tighten types of
<code>ArrowFunction</code>, <code>YieldExpression</code>,
<code>TSTypePredicate</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12373">#12373</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>rule-schema-to-typescript-types:</strong> respect ECMAScript
line terminators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12374">#12374</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/type-utils/CHANGELOG.md">@​typescript-eslint/type-utils's
changelog</a>.</em></p>
<blockquote>
<h2>8.61.0 (2026-06-08)</h2>
<p>This was a version bump only for type-utils to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16a5b247af"><code>16a5b24</code></a>
chore(release): publish 8.61.0</li>
<li><a
href="70a2b4bb8f"><code>70a2b4b</code></a>
chore: exclude OS-agnostic tests on Windows CI (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/type-utils/issues/12390">#12390</a>)</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.0/packages/type-utils">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/types` from 8.60.1 to 8.61.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/types's
releases</a>.</em></p>
<blockquote>
<h2>v8.61.0</h2>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
<li><strong>ast-spec:</strong> tighten types of
<code>ArrowFunction</code>, <code>YieldExpression</code>,
<code>TSTypePredicate</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12373">#12373</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>rule-schema-to-typescript-types:</strong> respect ECMAScript
line terminators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12374">#12374</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/types/CHANGELOG.md">@​typescript-eslint/types's
changelog</a>.</em></p>
<blockquote>
<h2>8.61.0 (2026-06-08)</h2>
<p>This was a version bump only for types to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16a5b247af"><code>16a5b24</code></a>
chore(release): publish 8.61.0</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.0/packages/types">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/typescript-estree` from 8.60.1 to 8.61.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/typescript-estree's
releases</a>.</em></p>
<blockquote>
<h2>v8.61.0</h2>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
<li><strong>ast-spec:</strong> tighten types of
<code>ArrowFunction</code>, <code>YieldExpression</code>,
<code>TSTypePredicate</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12373">#12373</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>rule-schema-to-typescript-types:</strong> respect ECMAScript
line terminators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12374">#12374</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-estree/CHANGELOG.md">@​typescript-eslint/typescript-estree's
changelog</a>.</em></p>
<blockquote>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16a5b247af"><code>16a5b24</code></a>
chore(release): publish 8.61.0</li>
<li><a
href="ef1fd28c68"><code>ef1fd28</code></a>
feat(ast-spec): change type of <code>UnaryExpression.prefix</code> to
always <code>true</code> (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-estree/issues/12">#12</a>...</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.0/packages/typescript-estree">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/utils` from 8.60.1 to 8.61.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/utils's
releases</a>.</em></p>
<blockquote>
<h2>v8.61.0</h2>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
<li><strong>ast-spec:</strong> tighten types of
<code>ArrowFunction</code>, <code>YieldExpression</code>,
<code>TSTypePredicate</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12373">#12373</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>rule-schema-to-typescript-types:</strong> respect ECMAScript
line terminators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12374">#12374</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/utils/CHANGELOG.md">@​typescript-eslint/utils's
changelog</a>.</em></p>
<blockquote>
<h2>8.61.0 (2026-06-08)</h2>
<p>This was a version bump only for utils to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16a5b247af"><code>16a5b24</code></a>
chore(release): publish 8.61.0</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.0/packages/utils">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/visitor-keys` from 8.60.1 to 8.61.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/visitor-keys's
releases</a>.</em></p>
<blockquote>
<h2>v8.61.0</h2>
<h2>8.61.0 (2026-06-08)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>ast-spec:</strong> change type of
<code>UnaryExpression.prefix</code> to always <code>true</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12372">#12372</a>)</li>
<li><strong>ast-spec:</strong> tighten types of
<code>ArrowFunction</code>, <code>YieldExpression</code>,
<code>TSTypePredicate</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12373">#12373</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>rule-schema-to-typescript-types:</strong> respect ECMAScript
line terminators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12374">#12374</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/visitor-keys/CHANGELOG.md">@​typescript-eslint/visitor-keys's
changelog</a>.</em></p>
<blockquote>
<h2>8.61.0 (2026-06-08)</h2>
<p>This was a version bump only for visitor-keys to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.61.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16a5b247af"><code>16a5b24</code></a>
chore(release): publish 8.61.0</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.0/packages/visitor-keys">compare
view</a></li>
</ul>
</details>
<br />

Updates `hono` from 4.12.24 to 4.12.25
<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.25</h2>
<h2>Security fixes</h2>
<p>This release includes fixes for the following security issues:</p>
<h3>CORS Middleware reflects any Origin with credentials when
<code>origin</code> defaults to the wildcard</h3>
<p>Affects: <code>hono/cors</code>. Fixes the wildcard origin reflecting
the request <code>Origin</code> and sending
<code>Access-Control-Allow-Credentials: true</code> when
<code>credentials: true</code> is set without an explicit
<code>origin</code>, where any site a logged-in user visited could make
credentialed cross-origin requests and read responses from
cookie-authenticated endpoints. GHSA-88fw-hqm2-52qc</p>
<h3>Body Limit Middleware can be bypassed on AWS Lambda by understating
<code>Content-Length</code></h3>
<p>Affects: <code>hono/body-limit</code> on AWS Lambda
(<code>hono/aws-lambda</code>, <code>hono/lambda-edge</code>). Fixes the
request being built with the client-declared <code>Content-Length</code>
while the body is delivered fully buffered, where a client could declare
a small <code>Content-Length</code> with a much larger body and slip
past the configured size limit. GHSA-rv63-4mwf-qqc2</p>
<h3>Path traversal in <code>serve-static</code> on Windows via encoded
backslash (<code>%5C</code>)</h3>
<p>Affects: <code>serveStatic</code> on Windows (Node, Bun, Deno
adapters). Fixes the path guard allowing a lone backslash, where an
encoded backslash (<code>%5C</code>) decoded to <code>\</code> was
treated as a separator by the Windows path resolver, letting a single
URL segment escape into a middleware-guarded subtree.
GHSA-wwfh-h76j-fc44</p>
<h3>AWS Lambda adapter merges multiple <code>Set-Cookie</code> headers
into one value, dropping cookies on ALB single-header and Lattice</h3>
<p>Affects: <code>hono/aws-lambda</code>. Fixes multiple
<code>Set-Cookie</code> response headers being joined into one
comma-separated value for ALB single-header responses and VPC Lattice
v2, where the value could not be split back into individual cookies and
clients silently dropped or misparsed them. GHSA-j6c9-x7qj-28xf</p>
<h3>Lambda@Edge adapter keeps only the last value of a repeated request
header, dropping the rest</h3>
<p>Affects: <code>hono/lambda-edge</code>. Fixes repeated request
headers being written with overwrite instead of append, where only the
last value of a header such as <code>X-Forwarded-For</code> reached the
application and the remaining values were silently dropped.
GHSA-wgpf-jwqj-8h8p</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="fce483e114"><code>fce483e</code></a>
4.12.25</li>
<li><a
href="751ba41ba2"><code>751ba41</code></a>
Merge commit from fork</li>
<li><a
href="f0b094db84"><code>f0b094d</code></a>
Merge commit from fork</li>
<li><a
href="fa5f9bfcc2"><code>fa5f9bf</code></a>
Merge commit from fork</li>
<li><a
href="3892a6c2b5"><code>3892a6c</code></a>
Merge commit from fork</li>
<li><a
href="74c2cf8ef4"><code>74c2cf8</code></a>
test(aws-lambda): update integration tests (<a
href="https://redirect.github.com/honojs/hono/issues/5012">#5012</a>)</li>
<li><a
href="7ae7cbae5d"><code>7ae7cba</code></a>
Merge commit from fork</li>
<li><a
href="1b13848154"><code>1b13848</code></a>
chore(ci): bump codecov-action to v7.0.0 (<a
href="https://redirect.github.com/honojs/hono/issues/5011">#5011</a>)</li>
<li>See full diff in <a
href="https://github.com/honojs/hono/compare/v4.12.24...v4.12.25">compare
view</a></li>
</ul>
</details>
<br />

Updates `semver` from 7.8.2 to 7.8.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/npm/node-semver/releases">semver's
releases</a>.</em></p>
<blockquote>
<h2>v7.8.3</h2>
<h2><a
href="https://github.com/npm/node-semver/compare/v7.8.2...v7.8.3">7.8.3</a>
(2026-06-08)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><a
href="046da7f527"><code>046da7f</code></a>
<a href="https://redirect.github.com/npm/node-semver/pull/872">#872</a>
align caret includePrerelease lower bounds (<a
href="https://redirect.github.com/npm/node-semver/issues/872">#872</a>)
(<a
href="https://github.com/wayyoungboy"><code>@​wayyoungboy</code></a>)</li>
</ul>
<h3>Chores</h3>
<ul>
<li><a
href="3485ddadb2"><code>3485dda</code></a>
<a href="https://redirect.github.com/npm/node-semver/pull/866">#866</a>
bump <code>@​npmcli/eslint-config</code> from 6.0.1 to 7.0.0 (<a
href="https://redirect.github.com/npm/node-semver/issues/866">#866</a>)
(<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot])</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/npm/node-semver/blob/main/CHANGELOG.md">semver's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/npm/node-semver/compare/v7.8.2...v7.8.3">7.8.3</a>
(2026-06-08)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><a
href="046da7f527"><code>046da7f</code></a>
<a href="https://redirect.github.com/npm/node-semver/pull/872">#872</a>
align caret includePrerelease lower bounds (<a
href="https://redirect.github.com/npm/node-semver/issues/872">#872</a>)
(<a
href="https://github.com/wayyoungboy"><code>@​wayyoungboy</code></a>)</li>
</ul>
<h3>Chores</h3>
<ul>
<li><a
href="3485ddadb2"><code>3485dda</code></a>
<a href="https://redirect.github.com/npm/node-semver/pull/866">#866</a>
bump <code>@​npmcli/eslint-config</code> from 6.0.1 to 7.0.0 (<a
href="https://redirect.github.com/npm/node-semver/issues/866">#866</a>)
(<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot])</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="6b77aa84a6"><code>6b77aa8</code></a>
chore: release 7.8.3 (<a
href="https://redirect.github.com/npm/node-semver/issues/873">#873</a>)</li>
<li><a
href="3485ddadb2"><code>3485dda</code></a>
chore: bump <code>@​npmcli/eslint-config</code> from 6.0.1 to 7.0.0 (<a
href="https://redirect.github.com/npm/node-semver/issues/866">#866</a>)</li>
<li><a
href="046da7f527"><code>046da7f</code></a>
fix: align caret includePrerelease lower bounds (<a
href="https://redirect.github.com/npm/node-semver/issues/872">#872</a>)</li>
<li>See full diff in <a
href="https://github.com/npm/node-semver/compare/v7.8.2...v7.8.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `side-channel` from 1.1.0 to 1.1.1
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ljharb/side-channel/blob/main/CHANGELOG.md">side-channel's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/ljharb/side-channel/compare/v1.1.0...v1.1.1">v1.1.1</a>
- 2026-06-08</h2>
<h3>Commits</h3>
<ul>
<li>[Fix] <code>assert</code>: do not observably access object keys when
throwing <a
href="fc17361513"><code>fc17361</code></a></li>
<li>[actions] update workflows <a
href="35b18c0bce"><code>35b18c0</code></a></li>
<li>[Dev Deps] update <code>@arethetypeswrong/cli</code>,
<code>@ljharb/eslint-config</code>, <code>@ljharb/tsconfig</code>,
<code>@types/tape</code>, <code>auto-changelog</code>,
<code>eslint</code>, <code>npmignore</code> <a
href="b456a01396"><code>b456a01</code></a></li>
<li>[Deps] update <code>object-inspect</code>,
<code>side-channel-list</code> <a
href="accf1a1a27"><code>accf1a1</code></a></li>
<li>[readme] replace runkit CI badge with shields.io check-runs badge <a
href="7e0c956411"><code>7e0c956</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3d260956bc"><code>3d26095</code></a>
v1.1.1</li>
<li><a
href="fc17361513"><code>fc17361</code></a>
[Fix] <code>assert</code>: do not observably access object keys when
throwing</li>
<li><a
href="35b18c0bce"><code>35b18c0</code></a>
[actions] update workflows</li>
<li><a
href="accf1a1a27"><code>accf1a1</code></a>
[Deps] update <code>object-inspect</code>,
<code>side-channel-list</code></li>
<li><a
href="b456a01396"><code>b456a01</code></a>
[Dev Deps] update <code>@arethetypeswrong/cli</code>,
<code>@ljharb/eslint-config</code>, `@ljharb/...</li>
<li><a
href="7e0c956411"><code>7e0c956</code></a>
[readme] replace runkit CI badge with shields.io check-runs badge</li>
<li>See full diff in <a
href="https://github.com/ljharb/side-channel/compare/v1.1.0...v1.1.1">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>
2026-06-09 17:09:28 +02:00
acp-release-bot[bot]
e692462249 chore(main): release 0.43.0 (#752)
🤖 I have created a release *beep* *boop*
---


##
[0.43.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.42.0...v0.43.0)
(2026-06-09)


### Features

* Add experimental elicitation support
([#756](https://github.com/agentclientprotocol/claude-agent-acp/issues/756))
([12bd276](12bd2762d9))
* **deps:** update to @anthropic-ai/claude-agent-sdk 0.3.169
([#754](https://github.com/agentclientprotocol/claude-agent-acp/issues/754))
([bd0ae4d](bd0ae4dde1))
* Update ACP SDK to 0.25.0
([#753](https://github.com/agentclientprotocol/claude-agent-acp/issues/753))
([0dbccf5](0dbccf588f))


### Bug Fixes

* Forward unstreamed assistant text blocks
([#757](https://github.com/agentclientprotocol/claude-agent-acp/issues/757))
([7ff6b7f](7ff6b7fd6b))
* Validate cwd before creating sessions
([#751](https://github.com/agentclientprotocol/claude-agent-acp/issues/751))
([9854b0c](9854b0c679)),
closes
[#749](https://github.com/agentclientprotocol/claude-agent-acp/issues/749)

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
2026-06-09 08:58:23 +00:00
dependabot[bot]
bd0ae4dde1 feat(deps): update to @anthropic-ai/claude-agent-sdk 0.3.169 (#754)
Bumps the minor group with 12 updates:

| Package | From | To |
| --- | --- | --- |
|
[@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript)
| `0.3.165` | `0.3.168` |
|
[@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript)
| `0.100.1` | `0.102.0` |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `25.9.1` | `25.9.2` |
| @anthropic-ai/claude-agent-sdk-darwin-arm64 | `0.3.165` | `0.3.168` |
| @anthropic-ai/claude-agent-sdk-darwin-x64 | `0.3.165` | `0.3.168` |
| @anthropic-ai/claude-agent-sdk-linux-arm64 | `0.3.165` | `0.3.168` |
| @anthropic-ai/claude-agent-sdk-linux-arm64-musl | `0.3.165` |
`0.3.168` |
| @anthropic-ai/claude-agent-sdk-linux-x64 | `0.3.165` | `0.3.168` |
| @anthropic-ai/claude-agent-sdk-linux-x64-musl | `0.3.165` | `0.3.168`
|
| @anthropic-ai/claude-agent-sdk-win32-arm64 | `0.3.165` | `0.3.168` |
| @anthropic-ai/claude-agent-sdk-win32-x64 | `0.3.165` | `0.3.168` |
| [hono](https://github.com/honojs/hono) | `4.12.23` | `4.12.24` |

Updates `@anthropic-ai/claude-agent-sdk` from 0.3.165 to 0.3.168
<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.168</h2>
<h2>What's changed</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.168</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.168
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.168
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.168
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.168
</code></pre>
<h2>v0.3.167</h2>
<h2>What's changed</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.167</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.167
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.167
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.167
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.167
</code></pre>
<h2>v0.3.166</h2>
<h2>What's changed</h2>
<ul>
<li>Fixed MCP resource tools not being injected for servers added at
runtime via the <code>mcp_set_servers</code> control request</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.166
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.166
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.166
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.166
</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.168</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.168</li>
</ul>
<h2>0.3.167</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.167</li>
</ul>
<h2>0.3.166</h2>
<ul>
<li>Fixed MCP resource tools not being injected for servers added at
runtime via the <code>mcp_set_servers</code> control request</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="2c2eeb8ce4"><code>2c2eeb8</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="3e35050adc"><code>3e35050</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="bc853fcd68"><code>bc853fc</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.165...v0.3.168">compare
view</a></li>
</ul>
</details>
<br />

Updates `@anthropic-ai/sdk` from 0.100.1 to 0.102.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.102.0</h2>
<h2>0.102.0 (2026-06-06)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.101.0...sdk-v0.102.0">sdk-v0.101.0...sdk-v0.102.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> small updates to Managed Agents types (<a
href="8ba4f92df3">8ba4f92</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>client:</strong> run middleware before request signing (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/45">#45</a>)
(<a
href="95f1a4a406">95f1a4a</a>)</li>
</ul>
<h2>sdk: v0.101.0</h2>
<h2>0.101.0 (2026-06-05)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.100.1...sdk-v0.101.0">sdk-v0.100.1...sdk-v0.101.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>client:</strong> add support for middleware (<a
href="9b01120796">9b01120</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>apply request timeout to inner fetch only, not middleware chain (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/40">#40</a>)
(<a
href="25c13f6183">25c13f6</a>)</li>
<li><strong>streaming:</strong> carry stop_details through beta
message_delta accumulation (<a
href="ed3fec7ace">ed3fec7</a>)</li>
<li><strong>streaming:</strong> correctly parse json numbers with
scientific notation (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/9">#9</a>)
(<a
href="7d5e6422dc">7d5e642</a>)</li>
</ul>
<h3>Chores</h3>
<ul>
<li><strong>internal:</strong> fix artifact url (<a
href="925ec276fa">925ec27</a>)</li>
<li><strong>internal:</strong> fix branch names (<a
href="fa3cf2c491">fa3cf2c</a>)</li>
<li><strong>internal:</strong> update private repo name (<a
href="a8ac213b8c">a8ac213</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>point security reports to Anthropic's HackerOne program (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/16">#16</a>)
(<a
href="5c7912c36e">5c7912c</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.102.0 (2026-06-06)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.101.0...sdk-v0.102.0">sdk-v0.101.0...sdk-v0.102.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> small updates to Managed Agents types (<a
href="8ba4f92df3">8ba4f92</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>client:</strong> run middleware before request signing (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/45">#45</a>)
(<a
href="95f1a4a406">95f1a4a</a>)</li>
</ul>
<h2>0.101.0 (2026-06-05)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.100.1...sdk-v0.101.0">sdk-v0.100.1...sdk-v0.101.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>client:</strong> add support for middleware (<a
href="9b01120796">9b01120</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>apply request timeout to inner fetch only, not middleware chain (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/40">#40</a>)
(<a
href="25c13f6183">25c13f6</a>)</li>
<li><strong>streaming:</strong> carry stop_details through beta
message_delta accumulation (<a
href="ed3fec7ace">ed3fec7</a>)</li>
<li><strong>streaming:</strong> correctly parse json numbers with
scientific notation (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/9">#9</a>)
(<a
href="7d5e6422dc">7d5e642</a>)</li>
</ul>
<h3>Chores</h3>
<ul>
<li><strong>internal:</strong> fix artifact url (<a
href="925ec276fa">925ec27</a>)</li>
<li><strong>internal:</strong> fix branch names (<a
href="fa3cf2c491">fa3cf2c</a>)</li>
<li><strong>internal:</strong> update private repo name (<a
href="a8ac213b8c">a8ac213</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>point security reports to Anthropic's HackerOne program (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/16">#16</a>)
(<a
href="5c7912c36e">5c7912c</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f7dfb972f6"><code>f7dfb97</code></a>
chore: release main</li>
<li><a
href="a3f3c97258"><code>a3f3c97</code></a>
feat(api): small updates to Managed Agents types</li>
<li><a
href="a5c98d117a"><code>a5c98d1</code></a>
fix(client): run middleware before request signing (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/45">#45</a>)</li>
<li><a
href="185ec061cf"><code>185ec06</code></a>
chore: release main</li>
<li><a
href="fab891069a"><code>fab8910</code></a>
codegen metadata</li>
<li><a
href="7ff4036756"><code>7ff4036</code></a>
fix: apply request timeout to inner fetch only, not middleware chain (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/40">#40</a>)</li>
<li><a
href="257bc1f56b"><code>257bc1f</code></a>
feat(client): add support for middleware</li>
<li><a
href="5b3ace5ec5"><code>5b3ace5</code></a>
chore(internal): fix artifact url</li>
<li><a
href="70966be8fc"><code>70966be</code></a>
fix(streaming): correctly parse json numbers with scientific notation
(<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/9">#9</a>)</li>
<li><a
href="7e22f20552"><code>7e22f20</code></a>
docs: point security reports to Anthropic's HackerOne program (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/16">#16</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.100.1...sdk-v0.102.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/node` from 25.9.1 to 25.9.2
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />

Updates `@anthropic-ai/claude-agent-sdk-darwin-arm64` from 0.3.165 to
0.3.168

Updates `@anthropic-ai/claude-agent-sdk-darwin-x64` from 0.3.165 to
0.3.168

Updates `@anthropic-ai/claude-agent-sdk-linux-arm64` from 0.3.165 to
0.3.168

Updates `@anthropic-ai/claude-agent-sdk-linux-arm64-musl` from 0.3.165
to 0.3.168

Updates `@anthropic-ai/claude-agent-sdk-linux-x64` from 0.3.165 to
0.3.168

Updates `@anthropic-ai/claude-agent-sdk-linux-x64-musl` from 0.3.165 to
0.3.168

Updates `@anthropic-ai/claude-agent-sdk-win32-arm64` from 0.3.165 to
0.3.168

Updates `@anthropic-ai/claude-agent-sdk-win32-x64` from 0.3.165 to
0.3.168

Updates `hono` from 4.12.23 to 4.12.24
<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.24</h2>
<h2>What's Changed</h2>
<ul>
<li>docs(contribution): simplifyAI Usage Policy by <a
href="https://github.com/yusukebe"><code>@​yusukebe</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4972">honojs/hono#4972</a></li>
<li>chore: remove <code>@​types/glob</code> by <a
href="https://github.com/rtritto"><code>@​rtritto</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4978">honojs/hono#4978</a></li>
<li>fix(bearer-auth): mention verifyToken in missing-options error
message by <a
href="https://github.com/tan7vir"><code>@​tan7vir</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4987">honojs/hono#4987</a></li>
<li>refactor(language): Test/improve tests on languages middleware by <a
href="https://github.com/iNeoO"><code>@​iNeoO</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4980">honojs/hono#4980</a></li>
<li>fix(utils/ipaddr): expand &quot;::&quot; to eight zero groups by <a
href="https://github.com/youcefzemmar"><code>@​youcefzemmar</code></a>
in <a
href="https://redirect.github.com/honojs/hono/pull/4973">honojs/hono#4973</a></li>
<li>fix: clean up config files trailing comma, stale excludes,
typesVersions gaps, jsr paths by <a
href="https://github.com/Mohammad-Faiz-Cloud-Engineer"><code>@​Mohammad-Faiz-Cloud-Engineer</code></a>
in <a
href="https://redirect.github.com/honojs/hono/pull/4982">honojs/hono#4982</a></li>
<li>refactor(timing): Test/add test for middleware timing by <a
href="https://github.com/iNeoO"><code>@​iNeoO</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4991">honojs/hono#4991</a></li>
<li>fix(utils/ipaddr): render the unspecified address binary as
&quot;::&quot; by <a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>
in <a
href="https://redirect.github.com/honojs/hono/pull/4998">honojs/hono#4998</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/honojs/hono/compare/v4.12.23...v4.12.24">https://github.com/honojs/hono/compare/v4.12.23...v4.12.24</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5fdde5ab5a"><code>5fdde5a</code></a>
4.12.24</li>
<li><a
href="c78932d745"><code>c78932d</code></a>
fix(utils/ipaddr): render the unspecified address binary as
&quot;::&quot; (<a
href="https://redirect.github.com/honojs/hono/issues/4998">#4998</a>)</li>
<li><a
href="5ef800ee45"><code>5ef800e</code></a>
refactor(timing): Test/add test for middleware timing (<a
href="https://redirect.github.com/honojs/hono/issues/4991">#4991</a>)</li>
<li><a
href="d743bb7dc0"><code>d743bb7</code></a>
fix: clean up config files trailing comma, stale excludes, typesVersions
gaps...</li>
<li><a
href="d22ff9c6fe"><code>d22ff9c</code></a>
fix(utils/ipaddr): expand &quot;::&quot; to eight zero groups (<a
href="https://redirect.github.com/honojs/hono/issues/4973">#4973</a>)</li>
<li><a
href="8e2cccc2fb"><code>8e2cccc</code></a>
refactor(language): Test/improve tests on languages middleware (<a
href="https://redirect.github.com/honojs/hono/issues/4980">#4980</a>)</li>
<li><a
href="413d3cbd0e"><code>413d3cb</code></a>
fix(bearer-auth): mention verifyToken in missing-options error message
(<a
href="https://redirect.github.com/honojs/hono/issues/4987">#4987</a>)</li>
<li><a
href="2cbeadda34"><code>2cbeadd</code></a>
chore: remove <code>@​types/glob</code> (<a
href="https://redirect.github.com/honojs/hono/issues/4978">#4978</a>)</li>
<li><a
href="9051d3e80a"><code>9051d3e</code></a>
docs(contribution): simplifyAI Usage Policy (<a
href="https://redirect.github.com/honojs/hono/issues/4972">#4972</a>)</li>
<li>See full diff in <a
href="https://github.com/honojs/hono/compare/v4.12.23...v4.12.24">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>
2026-06-09 08:56:57 +00:00
Ben Brandt
7ff6b7fd6b fix: Forward unstreamed assistant text blocks (#757) 2026-06-09 08:48:48 +00:00
Ben Brandt
12bd2762d9 feat: Add experimental elicitation support (#756)
This is still unstable on ACP, but experimenting with the latest api for
testing. Also allows for a representation of the AskUserQuestion tool
2026-06-09 09:59:06 +02:00
Ben Brandt
0dbccf588f feat: Update ACP SDK to 0.25.0 (#753) 2026-06-05 13:21:20 +00:00
Ben Brandt
9854b0c679 fix: Validate cwd before creating sessions (#751)
Closes #749
2026-06-05 11:04:47 +00:00
acp-release-bot[bot]
d877ee7133 chore(main): release 0.42.0 (#745)
🤖 I have created a release *beep* *boop*
---


##
[0.42.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.41.0...v0.42.0)
(2026-06-05)


### Features

* **deps-dev:** bump obug from 2.1.1 to 2.1.2 in the minor group
([#743](https://github.com/agentclientprotocol/claude-agent-acp/issues/743))
([7ffb78c](7ffb78c572))
* Send message ids for assistant and user messages
([#750](https://github.com/agentclientprotocol/claude-agent-acp/issues/750))
([18516a3](18516a3088))
* Update Claude Agent SDK to 0.3.165
([#746](https://github.com/agentclientprotocol/claude-agent-acp/issues/746))
([23cbe6f](23cbe6f58f))


### Bug Fixes

* Prevent cross-family model matching in resolveModelPreference
([#731](https://github.com/agentclientprotocol/claude-agent-acp/issues/731))
([f4704c1](f4704c168f))
* Prune tool cache per session after results
([#748](https://github.com/agentclientprotocol/claude-agent-acp/issues/748))
([ec14211](ec14211056))
* Update to better utilize existing SDK data
([#747](https://github.com/agentclientprotocol/claude-agent-acp/issues/747))
([398f763](398f763f71))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
2026-06-05 10:54:26 +00:00
Ben Brandt
18516a3088 feat: Send message ids for assistant and user messages (#750) 2026-06-05 10:53:21 +00:00
Ben Brandt
ec14211056 fix: Prune tool cache per session after results (#748) 2026-06-05 09:11:34 +00:00
Ben Brandt
398f763f71 fix: Update to better utilize existing SDK data (#747)
- **feat: Better surface permission, progress, and rate updates**
- **Use SDK context usage and thinking config**
- **Keep learned context size after compaction**
2026-06-05 08:59:06 +00:00
Ben Brandt
23cbe6f58f feat: Update Claude Agent SDK to 0.3.165 (#746) 2026-06-05 07:58:26 +00:00
dependabot[bot]
7ffb78c572 feat(deps-dev): bump obug from 2.1.1 to 2.1.2 in the minor group (#743)
Bumps the minor group with 1 update:
[obug](https://github.com/sxzz/obug).

Updates `obug` from 2.1.1 to 2.1.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sxzz/obug/releases">obug's
releases</a>.</em></p>
<blockquote>
<h2>v2.1.2</h2>
<p><em>No significant changes</em></p>
<h5>    <a
href="https://github.com/sxzz/obug/compare/v2.1.1...v2.1.2">View changes
on GitHub</a></h5>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="1f9500da5b"><code>1f9500d</code></a>
chore: release v2.1.2</li>
<li><a
href="6dcc07cd49"><code>6dcc07c</code></a>
chore: upgrade deps</li>
<li><a
href="9bcc620b15"><code>9bcc620</code></a>
ci: staging publish</li>
<li><a
href="c9688880be"><code>c968888</code></a>
chore: migrate tsdown config</li>
<li><a
href="2d652378be"><code>2d65237</code></a>
chore: add &quot;engines&quot; field to <code>package.json</code> (<a
href="https://redirect.github.com/sxzz/obug/issues/3">#3</a>)</li>
<li><a
href="a1191fd954"><code>a1191fd</code></a>
test: add file extensions to imports</li>
<li><a
href="b8239e5b9e"><code>b8239e5</code></a>
docs: add 'How to use' section to README [ci skip] (<a
href="https://redirect.github.com/sxzz/obug/issues/2">#2</a>)</li>
<li><a
href="55fe075260"><code>55fe075</code></a>
test: ignore dynamic import warning</li>
<li><a
href="a71beb8092"><code>a71beb8</code></a>
chore: update workflow references to main branch</li>
<li><a
href="e39bee9fcf"><code>e39bee9</code></a>
chore: upgrade deps</li>
<li>Additional commits viewable in <a
href="https://github.com/sxzz/obug/compare/v2.1.1...v2.1.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=obug&package-manager=npm_and_yarn&previous-version=2.1.1&new-version=2.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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>
2026-06-05 09:49:53 +02:00
Luca Dibattista
f4704c168f fix: Prevent cross-family model matching in resolveModelPreference (#731)
## Summary

Partially addresses #639. Builds on #702 which added version-aware
matching.

When the SDK's `opus` alias resolves to a newer version than what's in
`availableModels` (e.g. the SDK was bumped to Opus 4.8 but
`availableModels` still contains `claude-opus-4-6[1m]`), the version
check from #702 correctly rejects the `opus` entry (4.8 ≠ 4.6). However,
the tokenized matcher then falls through to `sonnet[1m]` because the
`1m` context hint token alone scores 3 points — enough to win. This
causes `claude-opus-4-6[1m]` to display as "Sonnet (1M context)".

Fix: `scoreModelMatch` now returns 0 when the only matching token is the
context hint. This prevents cross-family matches without hardcoding
model family names.

Note: Claude initially recommended hardcoding model families to be 100%
sure we avoid cross-family matching. While it would be a stronger
signal, I didn't find references to model families in the codebase
(except for opusplan --> opus) so I'm assuming the family list is
transparent to claude-agent-acp.

## Test plan

- [x] Added test: `does not inherit display info across mismatched model
families` — verifies `claude-opus-4-6[1m]` does not resolve to `Sonnet
(1M context)`
- [x] Existing tests pass (20/20 in `acp-agent-settings.test.ts`)
- [x] New test confirmed to fail on unmodified `main` and pass with the
fix

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-05 07:46:22 +00:00
15 changed files with 2446 additions and 341 deletions

View File

@@ -1,3 +1,3 @@
{
".": "0.41.0"
".": "0.45.0"
}

View File

@@ -1,5 +1,57 @@
# Changelog
## [0.45.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.44.0...v0.45.0) (2026-06-15)
### Features
* **deps-dev:** bump the minor group with 3 updates ([#763](https://github.com/agentclientprotocol/claude-agent-acp/issues/763)) ([7de5e4b](https://github.com/agentclientprotocol/claude-agent-acp/commit/7de5e4bcca9bfea70593092060f82bc8abe33e0e))
* **deps:** bump @anthropic-ai/claude-agent-sdk to 0.3.177 ([#771](https://github.com/agentclientprotocol/claude-agent-acp/issues/771)) ([1be5ca5](https://github.com/agentclientprotocol/claude-agent-acp/commit/1be5ca57ee772fe90e41126365dc4186a18ad257))
### Bug Fixes
* preserve ANTHROPIC_CUSTOM_MODEL_OPTION when availableModels is set ([#768](https://github.com/agentclientprotocol/claude-agent-acp/issues/768)) ([cc2885f](https://github.com/agentclientprotocol/claude-agent-acp/commit/cc2885f6a9993cf61e759c3c770015f94c218627))
## [0.44.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.43.0...v0.44.0) (2026-06-09)
### Features
* **deps:** bump the minor group with 14 updates ([#758](https://github.com/agentclientprotocol/claude-agent-acp/issues/758)) ([7a70162](https://github.com/agentclientprotocol/claude-agent-acp/commit/7a701623df2e05aec8e552bdfd1cf573413c3471))
* **deps:** update to @anthropic-ai/claude-agent-sdk 0.3.170 ([#761](https://github.com/agentclientprotocol/claude-agent-acp/issues/761)) ([d8af943](https://github.com/agentclientprotocol/claude-agent-acp/commit/d8af943a1efef9be27d771cec089d2f1cb56c523))
## [0.43.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.42.0...v0.43.0) (2026-06-09)
### Features
* Add experimental elicitation support ([#756](https://github.com/agentclientprotocol/claude-agent-acp/issues/756)) ([12bd276](https://github.com/agentclientprotocol/claude-agent-acp/commit/12bd2762d9ba0ccb7497a3848a5928b8bb2ce820))
* **deps:** update to @anthropic-ai/claude-agent-sdk 0.3.169 ([#754](https://github.com/agentclientprotocol/claude-agent-acp/issues/754)) ([bd0ae4d](https://github.com/agentclientprotocol/claude-agent-acp/commit/bd0ae4dde160f59559ab4ac1bf703fcf151eff17))
* Update ACP SDK to 0.25.0 ([#753](https://github.com/agentclientprotocol/claude-agent-acp/issues/753)) ([0dbccf5](https://github.com/agentclientprotocol/claude-agent-acp/commit/0dbccf588ff702015494dd29969d47f0b7402feb))
### Bug Fixes
* Forward unstreamed assistant text blocks ([#757](https://github.com/agentclientprotocol/claude-agent-acp/issues/757)) ([7ff6b7f](https://github.com/agentclientprotocol/claude-agent-acp/commit/7ff6b7fd6b157aedbce0c636087f51ae7d1df3a7))
* Validate cwd before creating sessions ([#751](https://github.com/agentclientprotocol/claude-agent-acp/issues/751)) ([9854b0c](https://github.com/agentclientprotocol/claude-agent-acp/commit/9854b0c6790ebafff514e3c4c8e03a131f065a4a)), closes [#749](https://github.com/agentclientprotocol/claude-agent-acp/issues/749)
## [0.42.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.41.0...v0.42.0) (2026-06-05)
### Features
* **deps-dev:** bump obug from 2.1.1 to 2.1.2 in the minor group ([#743](https://github.com/agentclientprotocol/claude-agent-acp/issues/743)) ([7ffb78c](https://github.com/agentclientprotocol/claude-agent-acp/commit/7ffb78c57280fc32af77862896c0035ce0e690b7))
* Send message ids for assistant and user messages ([#750](https://github.com/agentclientprotocol/claude-agent-acp/issues/750)) ([18516a3](https://github.com/agentclientprotocol/claude-agent-acp/commit/18516a3088f847a38f5eee0fe81ad5ca3e2a751c))
* Update Claude Agent SDK to 0.3.165 ([#746](https://github.com/agentclientprotocol/claude-agent-acp/issues/746)) ([23cbe6f](https://github.com/agentclientprotocol/claude-agent-acp/commit/23cbe6f58fa9e10ad736f664c4cf94dbd1c6ad65))
### Bug Fixes
* Prevent cross-family model matching in resolveModelPreference ([#731](https://github.com/agentclientprotocol/claude-agent-acp/issues/731)) ([f4704c1](https://github.com/agentclientprotocol/claude-agent-acp/commit/f4704c168f917e6710f2b3cd49e1fef49613a469))
* Prune tool cache per session after results ([#748](https://github.com/agentclientprotocol/claude-agent-acp/issues/748)) ([ec14211](https://github.com/agentclientprotocol/claude-agent-acp/commit/ec142110561f680330c6976783805685c5c069ec))
* Update to better utilize existing SDK data ([#747](https://github.com/agentclientprotocol/claude-agent-acp/issues/747)) ([398f763](https://github.com/agentclientprotocol/claude-agent-acp/commit/398f763f71bd2fe2ca9e8269de120d26a744b7a8))
## [0.41.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.40.0...v0.41.0) (2026-06-04)

400
package-lock.json generated
View File

@@ -1,62 +1,62 @@
{
"name": "@agentclientprotocol/claude-agent-acp",
"version": "0.41.0",
"version": "0.45.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@agentclientprotocol/claude-agent-acp",
"version": "0.41.0",
"version": "0.45.0",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "0.24.0",
"@anthropic-ai/claude-agent-sdk": "0.3.162",
"@agentclientprotocol/sdk": "0.25.1",
"@anthropic-ai/claude-agent-sdk": "0.3.177",
"zod": "^3.25.0 || ^4.0.0"
},
"bin": {
"claude-agent-acp": "dist/index.js"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.100.1",
"@anthropic-ai/sdk": "0.104.1",
"@eslint/js": "10.0.1",
"@types/node": "25.9.1",
"@typescript-eslint/eslint-plugin": "8.60.1",
"@typescript-eslint/parser": "8.60.1",
"eslint": "10.4.1",
"@types/node": "25.9.3",
"@typescript-eslint/eslint-plugin": "8.61.0",
"@typescript-eslint/parser": "8.61.0",
"eslint": "10.5.0",
"eslint-config-prettier": "10.1.8",
"globals": "17.6.0",
"prettier": "3.8.3",
"prettier": "3.8.4",
"ts-node": "10.9.2",
"typescript": "6.0.3",
"vitest": "4.1.8"
"vitest": "4.1.9"
}
},
"node_modules/@agentclientprotocol/sdk": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.24.0.tgz",
"integrity": "sha512-vvu9appvGvfYstBj19C6NCepV6SvUhY5VRv60KUZ4XzhTah/olOYul5Zo4C+x2enyshMSvgB2mm/OEmrsHaSmA==",
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.25.1.tgz",
"integrity": "sha512-jx2rF3bdpGwZ75Q/meyEDLLbYmbtxk82Uh9hDCdxDvcEedBnNSF5hZAnL/kJR5VNz56JqwOmqnAqasC84MwwkQ==",
"license": "Apache-2.0",
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"node_modules/@anthropic-ai/claude-agent-sdk": {
"version": "0.3.162",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.162.tgz",
"integrity": "sha512-piAlpc1h6FUMCNU+bnYNNLX1lOgMlFnqZmIhn6Myv48MaNRaecFaZEuVLY+UxV0kjGiQFYHBLR4fk/rRwi2SAA==",
"version": "0.3.177",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.177.tgz",
"integrity": "sha512-CBzXnzR661q3AlfZzBjmIFQx0cxr36iJV3PExTYmPyGQX32qxtiFQgnxTcF8wB4hcSVf2hnoy/gprVJdkNx7cw==",
"license": "SEE LICENSE IN README.md",
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.162",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.162",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.162",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.162",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.162",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.162",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.162",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.162"
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.177",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.177",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.177",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.177",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.177",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.177",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.177",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.177"
},
"peerDependencies": {
"@anthropic-ai/sdk": ">=0.93.0",
@@ -65,9 +65,9 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
"version": "0.3.162",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.162.tgz",
"integrity": "sha512-hafbfEtDeYko1rYCgIBAQbYnFXzd/hHf3IcoaD8mmlCQOAQhKA8gT/RPdLuuzQHdOjEgqDGTNOU+IjwL+msYcw==",
"version": "0.3.177",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.177.tgz",
"integrity": "sha512-u9Ty+KPllm2nw0RatdPF0zcPRquNZjVptmyLG0DqduGbgZDLQpfPFMF5hffFIRnVaXhx7+jkUmEdw0jrda0UcA==",
"cpu": [
"arm64"
],
@@ -78,9 +78,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
"version": "0.3.162",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.162.tgz",
"integrity": "sha512-BNg2Mh/4zc2Jsgpq7Mj8+UH8iJ9xzHS/0CmusBMik0H2Bn7Hra5R/f+csAfZOzSflQl4CNQdGOB2bir7d2n8Ww==",
"version": "0.3.177",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.177.tgz",
"integrity": "sha512-ona6Jv54XFwBTqOj3MzLWfKtc2m7Rdh58wOAX9Hnue/6FcWfyeuz/UDcidVTXQ7Xytz//Tb0JJgFtiQjO7FbIA==",
"cpu": [
"x64"
],
@@ -91,9 +91,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
"version": "0.3.162",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.162.tgz",
"integrity": "sha512-zCXYSimaXWQKZASfDJkoKXQr//toYDGIi16wKDh02Rqcr4mqFi9f5SBw/UCyimkGyYkNx3e+bmC+o/tFrLSTWw==",
"version": "0.3.177",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.177.tgz",
"integrity": "sha512-wBCbklkaDb483Ab4DUFfmJjZJKXz58YXPv+CiGsyjq1St19mbKEma5KKz3Ya9mlV8aLyh4zmLK2mEHPnF//Ipg==",
"cpu": [
"arm64"
],
@@ -104,9 +104,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
"version": "0.3.162",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.162.tgz",
"integrity": "sha512-Jr4cyfqzb5V2+p/1PynIfGROOI0JNtV3vBMAgckAdtDzpIFk4mV2T8936tsZzgZr04y40YH1HlQpnJDr92I+Fw==",
"version": "0.3.177",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.177.tgz",
"integrity": "sha512-v6PMDD3h2erLuTK5S2ZvExqdL3v44OyC70XpKhyqIUnyPaGR9YAMjh//EKdWC+mNvt6mIbRZpaDcHtbASVW8Rw==",
"cpu": [
"arm64"
],
@@ -117,9 +117,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
"version": "0.3.162",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.162.tgz",
"integrity": "sha512-FO2+zDuSTsZ/5MqxIwExLxG0c2auA5wO6iICwZdUOWtboC6yU2AEgp4mzF0jbe1UOP0J27uODFpvz7uRpWipkg==",
"version": "0.3.177",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.177.tgz",
"integrity": "sha512-WDP6puwPHscggNAfvIxyUSHSAjfUEkGRfnMXEPBHOqO+qjX2KGxeE13/ih3EVioeBVOUIqPui6VB05vXLa6T9Q==",
"cpu": [
"x64"
],
@@ -130,9 +130,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
"version": "0.3.162",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.162.tgz",
"integrity": "sha512-gW9Gpk7W3w3zGFHBDyY8uer/PE6T0pB+emN1aafZAomfseIH1ixJ6ya5Fw2cIS/K0/4oR2pvu4AprlbRBtr45Q==",
"version": "0.3.177",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.177.tgz",
"integrity": "sha512-1cdEO06WoEsl1JnnLCPIlg/8x37GtBsTuj65gIpSjdrImNBjgIuMWVyceP4qaXhFjtQgYK1nx3TpaxEFVDOrDg==",
"cpu": [
"x64"
],
@@ -143,9 +143,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
"version": "0.3.162",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.162.tgz",
"integrity": "sha512-TZQifFDBdhzt1u6wbbpQ2AZcRkhNVYx1iZVfydAbs3L7ZMK264LjRPytBK4n4S413Is1XyLHbGMvEUNA3N+Tng==",
"version": "0.3.177",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.177.tgz",
"integrity": "sha512-xLgDWnZaYohtFrkgEIkGZdP+rp4sXxAMbbpEvEp0LK1vAYmJam/ztT2yoK6gfI58IbToJq1WGUEX2HVnE65yOg==",
"cpu": [
"arm64"
],
@@ -156,9 +156,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
"version": "0.3.162",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.162.tgz",
"integrity": "sha512-8ZYDgNxkGp47xwpcEZ6/qUXd4BByA8hTnNf4fJD6+P1wWi7Ofxc/d5jwXSYwajYvBvbbktQDthmGzjtoK3lXUw==",
"version": "0.3.177",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.177.tgz",
"integrity": "sha512-SIdQLbtF//rYK4KDBNpUPyjyui7NwCFPZ2/3vyW3TR8R8xynkNq3cLis90FnPSgxaSshi38vkJYWh+9kGDB7zg==",
"cpu": [
"x64"
],
@@ -169,9 +169,9 @@
]
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.100.1",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.100.1.tgz",
"integrity": "sha512-RANcEe7LpiLczkKGOwoXOTuFdPhuubS0i4xaAKOMpcqc55YO0mukgxppV7eygx3DXNjxWT6RYOLPyOy0aIAmwg==",
"version": "0.104.1",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.104.1.tgz",
"integrity": "sha512-gGACa/+IaiXzRRmF96aOhamoBgapKRBiFWbmmTFP8aMkpaEcuStF+Q61bjo4vPxBM7gqWJNZqsngslRdnLHv0Q==",
"license": "MIT",
"dependencies": {
"json-schema-to-ts": "^3.1.1",
@@ -509,14 +509,14 @@
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.1"
"@tybys/wasm-util": "^0.10.2"
},
"funding": {
"type": "github",
@@ -893,9 +893,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
"version": "25.9.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz",
"integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -903,17 +903,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz",
"integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==",
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz",
"integrity": "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.60.1",
"@typescript-eslint/type-utils": "8.60.1",
"@typescript-eslint/utils": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1",
"@typescript-eslint/scope-manager": "8.61.0",
"@typescript-eslint/type-utils": "8.61.0",
"@typescript-eslint/utils": "8.61.0",
"@typescript-eslint/visitor-keys": "8.61.0",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -926,22 +926,22 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "8.60.1",
"@typescript-eslint/parser": "^8.61.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz",
"integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==",
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.0.tgz",
"integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.60.1",
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1",
"@typescript-eslint/scope-manager": "8.61.0",
"@typescript-eslint/types": "8.61.0",
"@typescript-eslint/typescript-estree": "8.61.0",
"@typescript-eslint/visitor-keys": "8.61.0",
"debug": "^4.4.3"
},
"engines": {
@@ -957,14 +957,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz",
"integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==",
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.0.tgz",
"integrity": "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.60.1",
"@typescript-eslint/types": "^8.60.1",
"@typescript-eslint/tsconfig-utils": "^8.61.0",
"@typescript-eslint/types": "^8.61.0",
"debug": "^4.4.3"
},
"engines": {
@@ -979,14 +979,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz",
"integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==",
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz",
"integrity": "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1"
"@typescript-eslint/types": "8.61.0",
"@typescript-eslint/visitor-keys": "8.61.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -997,9 +997,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz",
"integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==",
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz",
"integrity": "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1014,15 +1014,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz",
"integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==",
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz",
"integrity": "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1",
"@typescript-eslint/utils": "8.60.1",
"@typescript-eslint/types": "8.61.0",
"@typescript-eslint/typescript-estree": "8.61.0",
"@typescript-eslint/utils": "8.61.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -1039,9 +1039,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz",
"integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==",
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz",
"integrity": "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1053,16 +1053,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz",
"integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==",
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz",
"integrity": "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.60.1",
"@typescript-eslint/tsconfig-utils": "8.60.1",
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1",
"@typescript-eslint/project-service": "8.61.0",
"@typescript-eslint/tsconfig-utils": "8.61.0",
"@typescript-eslint/types": "8.61.0",
"@typescript-eslint/visitor-keys": "8.61.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -1081,16 +1081,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz",
"integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==",
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.0.tgz",
"integrity": "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.60.1",
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1"
"@typescript-eslint/scope-manager": "8.61.0",
"@typescript-eslint/types": "8.61.0",
"@typescript-eslint/typescript-estree": "8.61.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1105,13 +1105,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz",
"integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==",
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz",
"integrity": "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/types": "8.61.0",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -1136,16 +1136,16 @@
}
},
"node_modules/@vitest/expect": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz",
"integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==",
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
"integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
"@vitest/spy": "4.1.8",
"@vitest/utils": "4.1.8",
"@vitest/spy": "4.1.9",
"@vitest/utils": "4.1.9",
"chai": "^6.2.2",
"tinyrainbow": "^3.1.0"
},
@@ -1154,13 +1154,13 @@
}
},
"node_modules/@vitest/mocker": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz",
"integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==",
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz",
"integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "4.1.8",
"@vitest/spy": "4.1.9",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -1181,9 +1181,9 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz",
"integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==",
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz",
"integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1194,13 +1194,13 @@
}
},
"node_modules/@vitest/runner": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz",
"integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==",
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz",
"integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.1.8",
"@vitest/utils": "4.1.9",
"pathe": "^2.0.3"
},
"funding": {
@@ -1208,14 +1208,14 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz",
"integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==",
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz",
"integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.1.8",
"@vitest/utils": "4.1.8",
"@vitest/pretty-format": "4.1.9",
"@vitest/utils": "4.1.9",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -1224,9 +1224,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz",
"integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==",
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz",
"integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1234,13 +1234,13 @@
}
},
"node_modules/@vitest/utils": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz",
"integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==",
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz",
"integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.1.8",
"@vitest/pretty-format": "4.1.9",
"convert-source-map": "^2.0.0",
"tinyrainbow": "^3.1.0"
},
@@ -1263,9 +1263,9 @@
}
},
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
"integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -1361,21 +1361,21 @@
}
},
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
"integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"content-type": "^2.0.0",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"http-errors": "^2.0.1",
"iconv-lite": "^0.7.2",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
"qs": "^6.15.2",
"raw-body": "^3.0.2",
"type-is": "^2.1.0"
},
"engines": {
"node": ">=18"
@@ -1385,6 +1385,20 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/body-parser/node_modules/content-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/brace-expansion": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
@@ -1686,11 +1700,14 @@
}
},
"node_modules/eslint": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz",
"integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz",
"integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==",
"dev": true,
"license": "MIT",
"workspaces": [
"packages/*"
],
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
@@ -2313,9 +2330,9 @@
}
},
"node_modules/hono": {
"version": "4.12.23",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz",
"integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==",
"version": "4.12.25",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz",
"integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==",
"license": "MIT",
"peer": true,
"engines": {
@@ -2954,15 +2971,18 @@
}
},
"node_modules/obug": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
"integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
"dev": true,
"funding": [
"https://github.com/sponsors/sxzz",
"https://opencollective.com/debug"
],
"license": "MIT"
"license": "MIT",
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
@@ -3154,9 +3174,9 @@
}
},
"node_modules/prettier": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"version": "3.8.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz",
"integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==",
"dev": true,
"license": "MIT",
"bin": {
@@ -3304,9 +3324,9 @@
"peer": true
},
"node_modules/semver": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
"version": "7.8.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
"integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -3392,15 +3412,15 @@
}
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"object-inspect": "^1.13.4",
"side-channel-list": "^1.0.1",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
@@ -3826,19 +3846,19 @@
}
},
"node_modules/vitest": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz",
"integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==",
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz",
"integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "4.1.8",
"@vitest/mocker": "4.1.8",
"@vitest/pretty-format": "4.1.8",
"@vitest/runner": "4.1.8",
"@vitest/snapshot": "4.1.8",
"@vitest/spy": "4.1.8",
"@vitest/utils": "4.1.8",
"@vitest/expect": "4.1.9",
"@vitest/mocker": "4.1.9",
"@vitest/pretty-format": "4.1.9",
"@vitest/runner": "4.1.9",
"@vitest/snapshot": "4.1.9",
"@vitest/spy": "4.1.9",
"@vitest/utils": "4.1.9",
"es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0",
"magic-string": "^0.30.21",
@@ -3866,12 +3886,12 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.1.8",
"@vitest/browser-preview": "4.1.8",
"@vitest/browser-webdriverio": "4.1.8",
"@vitest/coverage-istanbul": "4.1.8",
"@vitest/coverage-v8": "4.1.8",
"@vitest/ui": "4.1.8",
"@vitest/browser-playwright": "4.1.9",
"@vitest/browser-preview": "4.1.9",
"@vitest/browser-webdriverio": "4.1.9",
"@vitest/coverage-istanbul": "4.1.9",
"@vitest/coverage-v8": "4.1.9",
"@vitest/ui": "4.1.9",
"happy-dom": "*",
"jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"

View File

@@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "0.41.0",
"version": "0.45.0",
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
"main": "dist/lib.js",
"types": "dist/lib.d.ts",
@@ -60,22 +60,22 @@
"author": "Zed Industries",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "0.24.0",
"@anthropic-ai/claude-agent-sdk": "0.3.162",
"@agentclientprotocol/sdk": "0.25.1",
"@anthropic-ai/claude-agent-sdk": "0.3.177",
"zod": "^3.25.0 || ^4.0.0"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.100.1",
"@anthropic-ai/sdk": "0.104.1",
"@eslint/js": "10.0.1",
"@types/node": "25.9.1",
"@typescript-eslint/eslint-plugin": "8.60.1",
"@typescript-eslint/parser": "8.60.1",
"eslint": "10.4.1",
"@types/node": "25.9.3",
"@typescript-eslint/eslint-plugin": "8.61.0",
"@typescript-eslint/parser": "8.61.0",
"eslint": "10.5.0",
"eslint-config-prettier": "10.1.8",
"globals": "17.6.0",
"prettier": "3.8.3",
"prettier": "3.8.4",
"ts-node": "10.9.2",
"typescript": "6.0.3",
"vitest": "4.1.8"
"vitest": "4.1.9"
}
}

View File

@@ -36,8 +36,6 @@ import {
CloseSessionResponse,
DeleteSessionRequest,
DeleteSessionResponse,
TerminalHandle,
TerminalOutputResponse,
WriteTextFileRequest,
WriteTextFileResponse,
StopReason,
@@ -50,8 +48,10 @@ import {
McpServerConfig,
ModelInfo,
ModelUsage,
OnElicitation,
Options,
PermissionMode,
PermissionResult,
PermissionUpdate,
Query,
query,
@@ -62,13 +62,24 @@ import {
SDKPartialAssistantMessage,
SDKUserMessage,
SlashCommand,
ThinkingConfig,
} from "@anthropic-ai/claude-agent-sdk";
import { ContentBlockParam } from "@anthropic-ai/sdk/resources";
import { BetaContentBlock, BetaRawContentBlockDelta } from "@anthropic-ai/sdk/resources/beta.mjs";
import { randomUUID } from "node:crypto";
import type { Stats } from "node:fs";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import packageJson from "../package.json" with { type: "json" };
import {
applyAskElicitationResponse,
askUserQuestionsToCreateRequest,
createElicitationResponseToElicitResult,
ElicitationSupport,
extractAskUserQuestions,
mcpElicitationToCreateRequest,
} from "./elicitation.js";
import { SettingsManager } from "./settings.js";
import {
applyTaskCreate,
@@ -194,6 +205,30 @@ type Session = {
/** Accumulated task list for the session, keyed by task ID. Task IDs are
* per-session, so this state must not be shared across sessions. */
taskState: TaskState;
/** Caches `tool_use` blocks by id so the matching `tool_result` can recover
* the tool name/input when mapping it to a `tool_call_update`. Per-session
* (tool_use ids are only unique within a session) and pruned at
* `tool_result` time so a long-running session doesn't accumulate every
* tool call for its whole lifetime. */
toolUseCache: ToolUseCache;
/** Maps the ACP `messageId` we expose to clients (see `messageIdForGrouping`)
* to the SDK message uuid that the Agent SDK's rewind/resume APIs key on
* (`Query.rewindFiles` takes a user-message uuid; `resumeSessionAt` takes an
* `SDKAssistantMessage.uuid`). For assistant turns the two differ — the ACP
* id is the Anthropic API message id (`msg_…`), available at `message_start`
* so streamed chunks can carry it, while the uuid only arrives on the
* consolidated message — so a client can only ask to rewind/fork by the id it
* was given, and we need this table to translate it back.
*
* Populated as a byproduct of the message loop (the consolidated message
* carries both ids) and of `replaySessionHistory` on load, so no extra
* `getSessionMessages` read is needed at rewind time. Last-write-wins
* naturally yields the turn-boundary uuid when one `msg_…` spans several
* content-block messages.
*
* NOT READ YET — recorded now so the mapping exists if/when we wire up
* fork/rewind. */
messageIdToUuid: Map<string, string>;
};
/** Compute a stable fingerprint of the session-defining params so we can
@@ -208,17 +243,6 @@ function computeSessionFingerprint(params: {
return JSON.stringify({ cwd: params.cwd, mcpServers: servers });
}
type BackgroundTerminal =
| {
handle: TerminalHandle;
status: "started";
lastOutput: TerminalOutputResponse | null;
}
| {
status: "aborted" | "exited" | "killed" | "timedOut";
pendingOutput: TerminalOutputResponse;
};
export type SDKMessageFilter = {
type: string;
subtype?: string;
@@ -547,8 +571,6 @@ export class ClaudeAcpAgent implements Agent {
[key: string]: Session;
};
client: AgentSideConnection;
toolUseCache: ToolUseCache;
backgroundTerminals: { [key: string]: BackgroundTerminal } = {};
clientCapabilities?: ClientCapabilities;
logger: Logger;
gatewayAuthRequest?: GatewayAuthRequest;
@@ -560,7 +582,6 @@ export class ClaudeAcpAgent implements Agent {
constructor(client: AgentSideConnection, logger?: Logger) {
this.sessions = {};
this.client = client;
this.toolUseCache = {};
this.logger = logger ?? console;
}
@@ -830,6 +851,32 @@ export class ClaudeAcpAgent implements Agent {
// `compacting` status sets it again, so every distinct compaction (e.g.
// repeated auto-compactions in a long turn) is still shown.
let compactionInProgress = false;
// Holds the Anthropic API message id of the assistant message currently
// being streamed, captured from `message_start` so every streamed chunk can
// be tagged with it. We use the API message id rather than the
// per-`stream_event` uuid because the same id is also present on the
// consolidated assistant message and in the persisted transcript — so a turn
// keeps the same ACP `messageId` whether it is streamed live or replayed
// from history. The per-event uuid is unique per event and never persisted.
// A single value suffices because every streaming partial arrives with
// `parent_tool_use_id === null` (subagent work is folded into tool-result
// messages, never surfaced as partial streams).
let currentStreamMessageId: string | undefined;
// Per-message-id record of which assistant content actually streamed live via
// `stream_event` deltas, split by block type. The `assistant` case below
// normally drops `text`/`thinking` blocks on the assumption they already
// reached the client as `agent_message_chunk`/`agent_thought_chunk`. That
// breaks behind Anthropic-protocol gateways that return a turn as a single
// non-streamed block (common with OpenAI-compatible proxies): no
// `content_block_delta` fires, so the assembled block is the only copy the
// client will ever see. We keep the filter per (id, block type) for content
// that did stream, and fall back to forwarding what did not. Split by type so
// a gateway that streams text but not thinking (or vice versa) doesn't lose
// the un-streamed block. Only top-level (`parent_tool_use_id === null`)
// streams are recorded — subagent text is never streamed and must stay
// filtered, as it is internal to the tool call.
const streamedTextIds = new Set<string>();
const streamedThinkingIds = new Set<string>();
const userMessage = promptToClaude(params);
@@ -941,28 +988,35 @@ export class ClaudeAcpAgent implements Agent {
break;
}
case "compact_boundary": {
// Send used:0 immediately so the client doesn't keep showing
// the stale pre-compaction context size until the next turn.
// Refresh the displayed usage immediately so the client doesn't
// keep showing the stale pre-compaction size (e.g. "944k/1m")
// right after the user sees "Compacting completed", which is
// confusing and wrong.
//
// This is a deliberate approximation: we don't know the exact
// post-compaction token count (only the SDK's next API call
// reveals that). But used:0 is directionally correct — context
// just dropped dramatically — and the real value replaces it
// within seconds when the next result message arrives.
// The alternative (no update) leaves the client showing e.g.
// "944k/1m" right after the user sees "Compacting completed",
// which is confusing and wrong.
// Prefer the SDK's authoritative post-compaction `used` via
// getContextUsage — it reflects the real retained context
// (system prompt + tools + surviving messages), which the
// per-message API usage numbers can't give us until the next
// turn's result. If the control request fails, fall back to the
// used:0 approximation: directionally correct (context just
// dropped dramatically) and replaced within seconds by the next
// result message.
//
// `size` keeps coming from session.contextWindowSize (learned
// from modelUsage / the model heuristic) — getContextUsage's
// window field under-reports extended 1M windows.
//
// The "Compacting completed." text is emitted from the `status`
// handler (keyed on `compact_result`), not here, so the failure
// path gets a message too.
lastAssistantTotalUsage = 0;
const usedTokens = await fetchContextUsedTokens(session.query, this.logger);
lastAssistantUsage = null;
lastAssistantTotalUsage = usedTokens ?? 0;
await this.client.sessionUpdate({
sessionId: message.session_id,
update: {
sessionUpdate: "usage_update",
used: 0,
used: lastAssistantTotalUsage,
size: session.contextWindowSize,
},
});
@@ -1043,6 +1097,49 @@ export class ClaudeAcpAgent implements Agent {
});
break;
}
case "mirror_error": {
// The SDK failed to persist session history (SessionStore
// append rejected/timed out after retry) — potential data loss
// the user should know about rather than a silent gap on
// resume. Log it and surface a warning in the conversation.
this.logger.error(
`Session ${message.session_id}: failed to persist history: ${message.error}`,
);
break;
}
case "permission_denied": {
// A tool call was auto-denied (by a rule, the classifier,
// dontAsk mode, etc.) before running. The tool_use block was
// already emitted as a `tool_call`, so mark it failed with the
// rejection reason — otherwise the client shows a tool call
// that silently never resolves.
const reason = message.decision_reason ?? message.message;
await this.client.sessionUpdate({
sessionId: message.session_id,
update: {
sessionUpdate: "tool_call_update",
toolCallId: message.tool_use_id,
status: "failed",
content: [
{
type: "content",
content: { type: "text", text: `Permission denied: ${reason}` },
},
],
_meta: {
claudeCode: {
toolName: message.tool_name,
toolResponse: {
decisionReasonType: message.decision_reason_type,
decisionReason: message.decision_reason,
message: message.message,
},
},
} satisfies ToolUpdateMeta,
},
});
break;
}
case "hook_started":
case "hook_progress":
case "hook_response":
@@ -1051,13 +1148,27 @@ export class ClaudeAcpAgent implements Agent {
case "task_notification":
case "task_progress":
case "task_updated":
case "elicitation_complete":
break;
case "elicitation_complete": {
// A url-mode MCP elicitation finished server-side. Let the client
// dismiss any UI it opened for it. Only meaningful when the
// client supports url elicitation; ignore failures otherwise.
if (this.clientCapabilities?.elicitation?.url) {
try {
await this.client.unstable_completeElicitation({
elicitationId: message.elicitation_id,
});
} catch (error) {
this.logger.error(`Failed to complete elicitation: ${error}`);
}
}
break;
}
case "plugin_install":
case "notification":
case "api_retry":
case "mirror_error":
case "permission_denied":
case "thinking_tokens":
case "model_refusal_fallback":
// Todo: process via status api: https://docs.claude.com/en/docs/claude-code/hooks#hook-output
break;
default:
@@ -1161,7 +1272,7 @@ export class ClaudeAcpAgent implements Agent {
message.result,
"assistant",
params.sessionId,
this.toolUseCache,
session.toolUseCache,
this.client,
this.logger,
)) {
@@ -1208,6 +1319,28 @@ export class ClaudeAcpAgent implements Agent {
break;
}
case "stream_event": {
// `message_start` carries the Anthropic API message id; capture it
// so the streamed chunks that follow (whose delta events don't carry
// it) can all be tagged with the same, replay-stable id.
if (message.event.type === "message_start") {
currentStreamMessageId = message.event.message.id || undefined;
}
// Record that this top-level message id actually streamed text/
// thinking, so the `assistant` case below knows its assembled blocks
// are duplicates (filter them) rather than the only copy (forward
// them). Gated on `parent_tool_use_id === null` so a subagent stream
// can't attribute its content to the top-level message id.
if (
currentStreamMessageId &&
message.parent_tool_use_id === null &&
message.event.type === "content_block_delta"
) {
if (message.event.delta.type === "text_delta") {
streamedTextIds.add(currentStreamMessageId);
} else if (message.event.delta.type === "thinking_delta") {
streamedThinkingIds.add(currentStreamMessageId);
}
}
if (
message.parent_tool_use_id === null &&
(message.event.type === "message_start" || message.event.type === "message_delta")
@@ -1262,13 +1395,14 @@ export class ClaudeAcpAgent implements Agent {
for (const notification of streamEventToAcpNotifications(
message,
params.sessionId,
this.toolUseCache,
session.toolUseCache,
this.client,
this.logger,
{
clientCapabilities: this.clientCapabilities,
cwd: session.cwd,
taskState: session.taskState,
messageId: currentStreamMessageId,
},
)) {
await this.client.sessionUpdate(notification);
@@ -1281,6 +1415,15 @@ export class ClaudeAcpAgent implements Agent {
break;
}
// Record the ACP messageId -> SDK uuid mapping for this message. The
// consolidated message carries both ids, so this is where we learn
// the uuid that the SDK's rewind/resume APIs key on for the id we
// hand clients. Not read yet (see Session.messageIdToUuid).
const mappedMessageId = messageIdForGrouping(message);
if (mappedMessageId && typeof message.uuid === "string" && message.uuid.length > 0) {
session.messageIdToUuid.set(mappedMessageId, message.uuid);
}
// Check for prompt replay
if (message.type === "user" && "uuid" in message && message.uuid) {
if (message.uuid === promptUuid) {
@@ -1336,7 +1479,7 @@ export class ClaudeAcpAgent implements Agent {
stripped,
message.message.role,
params.sessionId,
this.toolUseCache,
session.toolUseCache,
this.client,
this.logger,
{
@@ -1344,6 +1487,7 @@ export class ClaudeAcpAgent implements Agent {
parentToolUseId: message.parent_tool_use_id,
cwd: session.cwd,
taskState: session.taskState,
messageId: messageIdForGrouping(message),
},
)) {
await this.client.sessionUpdate(notification);
@@ -1386,19 +1530,53 @@ export class ClaudeAcpAgent implements Agent {
throw RequestError.authRequired();
}
const content =
message.type === "assistant"
? // Handled by stream events above
message.message.content.filter(
(item) => !["text", "thinking"].includes(item.type),
)
: message.message.content;
let content: typeof message.message.content;
if (message.type === "assistant" && message.parent_tool_use_id === null) {
// Top-level assistant message: drop text/thinking blocks already
// streamed live as chunks, and forward (as a fallback) any that were
// not, so non-streaming gateways still deliver the final answer.
const id = messageIdForGrouping(message);
content = message.message.content.filter((item) => {
// Non-text blocks (tool_use, etc.) always pass through; their own
// dedupe (`toolUseCache`) collapses the streamed/assembled pair.
if (item.type !== "text" && item.type !== "thinking") {
return true;
}
// Already delivered live as a chunk of this exact type — drop the
// duplicate. Checked per type so streaming one (e.g. text) doesn't
// suppress an un-streamed block of the other (e.g. thinking).
const streamedLive =
id !== undefined &&
(item.type === "text" ? streamedTextIds : streamedThinkingIds).has(id);
if (streamedLive) {
return false;
}
// Empty assembled blocks carry nothing (some gateways emit an empty
// `thinking` block before the real text) — don't forward stray
// empty chunks.
const text = item.type === "text" ? item.text : item.thinking;
if (text.length === 0) {
return false;
}
return true;
});
} else if (message.type === "assistant") {
// Subagent assistant message (`parent_tool_use_id !== null`). It is
// never streamed live and its text/thinking is internal to the tool
// call — keep dropping it so subagent prose doesn't leak into the
// top-level feed.
content = message.message.content.filter(
(item) => item.type !== "text" && item.type !== "thinking",
);
} else {
content = message.message.content;
}
for (const notification of toAcpNotifications(
content,
message.message.role,
params.sessionId,
this.toolUseCache,
session.toolUseCache,
this.client,
this.logger,
{
@@ -1406,17 +1584,47 @@ export class ClaudeAcpAgent implements Agent {
parentToolUseId: message.parent_tool_use_id,
cwd: session.cwd,
taskState: session.taskState,
messageId: messageIdForGrouping(message),
},
)) {
await this.client.sessionUpdate(notification);
}
break;
}
case "tool_progress":
case "tool_progress": {
await this.client.sessionUpdate({
sessionId: message.session_id,
update: {
sessionUpdate: "tool_call_update",
toolCallId: message.tool_use_id,
status: "in_progress",
_meta: {
claudeCode: {
toolName: message.tool_name,
toolResponse: { elapsedTimeSeconds: message.elapsed_time_seconds },
},
} satisfies ToolUpdateMeta,
},
});
break;
}
case "rate_limit_event": {
if (lastAssistantTotalUsage !== null) {
await this.client.sessionUpdate({
sessionId: message.session_id,
update: {
sessionUpdate: "usage_update",
used: lastAssistantTotalUsage,
size: session.contextWindowSize,
_meta: { "_claude/rateLimit": message.rate_limit_info },
},
});
}
break;
}
case "tool_use_summary":
case "auth_status":
case "prompt_suggestion":
case "rate_limit_event":
break;
default:
unreachable(message);
@@ -1591,7 +1799,7 @@ export class ClaudeAcpAgent implements Agent {
return {};
}
async unstable_deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse> {
async deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse> {
// Tear down any active in-memory state first so the on-disk file isn't
// recreated by an outstanding query writing to it.
if (this.sessions[params.sessionId]) {
@@ -1718,6 +1926,16 @@ export class ClaudeAcpAgent implements Agent {
const messages = await getSessionMessages(sessionId);
for (const message of messages) {
// Backfill the ACP messageId -> SDK uuid mapping for messages we didn't
// observe live (resumed/loaded sessions), so rewind/resume can translate
// a client-supplied id without an extra getSessionMessages read. Not read
// yet (see Session.messageIdToUuid).
const replayMessageId = messageIdForGrouping(message);
const replaySession = this.sessions[sessionId];
if (replaySession && replayMessageId && message.uuid) {
replaySession.messageIdToUuid.set(replayMessageId, message.uuid);
}
// @ts-expect-error - untyped in SDK but we handle all of these
let content: unknown = message.message.content;
// @ts-expect-error - untyped in SDK but we handle all of these
@@ -1740,6 +1958,7 @@ export class ClaudeAcpAgent implements Agent {
clientCapabilities: this.clientCapabilities,
cwd: this.sessions[sessionId]?.cwd,
taskState: this.sessions[sessionId]?.taskState,
messageId: replayMessageId,
},
)) {
await this.client.sessionUpdate(notification);
@@ -1769,6 +1988,14 @@ export class ClaudeAcpAgent implements Agent {
};
}
// AskUserQuestion is surfaced to us as a normal permission check (the SDK
// routes it through canUseTool whenever a callback is registered, rather
// than the interactive dialog). Present it as an ACP form elicitation and
// feed the answers back as updatedInput for the tool's own call() to read.
if (toolName === "AskUserQuestion" && this.clientCapabilities?.elicitation?.form) {
return this.handleAskUserQuestion(sessionId, toolInput, toolUseID, signal);
}
if (toolName === "ExitPlanMode") {
const optionsAll: PermissionOption[] = [
{ kind: "allow_always", name: 'Yes, and use "auto" mode', optionId: "auto" },
@@ -1913,6 +2140,71 @@ export class ClaudeAcpAgent implements Agent {
};
}
/**
* Handle elicitation requests that originate from MCP servers by forwarding
* them to the client over ACP. Modes the client did not advertise (or
* requests we can't represent) are declined.
*/
private handleMcpElicitation(sessionId: string, support: ElicitationSupport): OnElicitation {
return async (request, { signal }) => {
const isUrl = request.mode === "url";
if ((isUrl && !support.url) || (!isUrl && !support.form)) {
return { action: "decline" };
}
const createRequest = mcpElicitationToCreateRequest(request, sessionId);
if (!createRequest) {
return { action: "decline" };
}
try {
const response = await this.client.unstable_createElicitation(createRequest);
if (signal.aborted) {
return { action: "cancel" };
}
return createElicitationResponseToElicitResult(response);
} catch (error) {
this.logger.error(`Failed to forward MCP elicitation: ${error}`);
return { action: "decline" };
}
};
}
/**
* Present the built-in AskUserQuestion tool's questions as an ACP form
* elicitation and return the answers as the tool's `updatedInput`. Called from
* `canUseTool` since that is where the SDK routes the tool's permission check.
*/
private async handleAskUserQuestion(
sessionId: string,
toolInput: Record<string, unknown>,
toolUseID: string,
signal: AbortSignal,
): Promise<PermissionResult> {
const questions = extractAskUserQuestions(toolInput);
if (!questions) {
return { behavior: "deny", message: "AskUserQuestion called with no valid questions." };
}
const createRequest = askUserQuestionsToCreateRequest(questions, sessionId, toolUseID);
let response;
try {
response = await this.client.unstable_createElicitation(createRequest);
} catch (error) {
this.logger.error(`Failed to present AskUserQuestion elicitation: ${error}`);
return { behavior: "deny", message: "Could not present the question to the user." };
}
if (signal.aborted) {
throw new Error("Tool use aborted");
}
const outcome = applyAskElicitationResponse(response, toolInput, questions);
if (outcome.action === "cancel") {
throw new Error("Tool use aborted");
}
return { behavior: "allow", updatedInput: outcome.updatedInput };
}
private async sendAvailableCommandsUpdate(sessionId: string): Promise<void> {
const session = this.sessions[sessionId];
if (!session) return;
@@ -2088,10 +2380,46 @@ export class ClaudeAcpAgent implements Agent {
};
}
/**
* Ensures the requested `cwd` is an absolute path that points at an existing
* directory before we create a session. Throws an `invalidParams` error with
* an actionable message so clients (e.g. Zed) can surface it to the user
* instead of failing later with an opaque SDK error.
*/
private async validateCwd(cwd: string): Promise<void> {
if (!path.isAbsolute(cwd)) {
throw RequestError.invalidParams(
{ cwd },
`\`cwd\` must be an absolute path, but received: ${cwd}`,
);
}
let stats: Stats;
try {
stats = await fs.stat(cwd);
} catch {
throw RequestError.invalidParams(
{ cwd },
`\`cwd\` does not exist on the machine running the agent: ${cwd}`,
);
}
if (!stats.isDirectory()) {
throw RequestError.invalidParams({ cwd }, `\`cwd\` is not a directory: ${cwd}`);
}
}
private async createSession(
params: NewSessionRequest,
creationOpts: { resume?: string; forkSession?: boolean } = {},
): Promise<NewSessionResponse> {
// Validate `cwd` up front. The ACP spec requires an absolute path, and the
// directory must actually exist on the machine running the agent. Without
// this check a session is created against a missing directory and the
// failure only surfaces later as a confusing "native binary failed to
// launch" error from the SDK (see issue #749).
await this.validateCwd(params.cwd);
// We want to create a new session id unless it is resume,
// but not resume + forkSession.
let sessionId;
@@ -2165,16 +2493,24 @@ export class ClaudeAcpAgent implements Agent {
const sessionMeta = params._meta as NewSessionMeta | undefined;
const userProvidedOptions = sessionMeta?.claudeCode?.options;
// Configure thinking tokens from environment variable
const maxThinkingTokens = process.env.MAX_THINKING_TOKENS
? parseInt(process.env.MAX_THINKING_TOKENS, 10)
: undefined;
// Configure thinking behavior from environment variable
const thinking = resolveThinkingConfig(process.env.MAX_THINKING_TOKENS, this.logger);
// Parse model configuration from environment (e.g. Bedrock model overrides)
const modelConfig = parseModelConfig(process.env.CLAUDE_MODEL_CONFIG);
// Disable this for now, not a great way to expose this over ACP at the moment (in progress work so we can revisit)
const disallowedTools = ["AskUserQuestion"];
// Elicitation modes the connected client advertised. We only forward
// elicitations (and only re-enable AskUserQuestion) for modes the client
// can actually render.
const elicitationSupport: ElicitationSupport = {
form: !!this.clientCapabilities?.elicitation?.form,
url: !!this.clientCapabilities?.elicitation?.url,
};
// AskUserQuestion surfaces as a `permission_ask_user_question` dialog that
// we render as a form elicitation. Without form-elicitation support there
// is no way to present it over ACP, so keep it disabled in that case.
const disallowedTools = elicitationSupport.form ? [] : ["AskUserQuestion"];
// Resolve which built-in tools to expose.
// Explicit tools array from _meta.claudeCode.options takes precedence.
@@ -2194,7 +2530,7 @@ export class ClaudeAcpAgent implements Agent {
const options: Options = {
systemPrompt,
settingSources: ["user", "project", "local"],
...(maxThinkingTokens !== undefined && { maxThinkingTokens }),
...(thinking !== undefined && { thinking }),
...userProvidedOptions,
// CLAUDE_MODEL_CONFIG env var is a fallback for model
// configuration (e.g. Bedrock model ID overrides). When the caller
@@ -2223,6 +2559,13 @@ export class ClaudeAcpAgent implements Agent {
allowDangerouslySkipPermissions: ALLOW_BYPASS,
permissionMode,
canUseTool: this.canUseTool(sessionId),
// Forward MCP elicitation requests onto ACP elicitation. Only attached
// when the client advertised support, so non-supporting clients keep the
// SDK's default (auto-decline) behavior. (AskUserQuestion is handled in
// canUseTool, not here.)
...(elicitationSupport.form || elicitationSupport.url
? { onElicitation: this.handleMcpElicitation(sessionId, elicitationSupport) }
: {}),
pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE ?? (await claudeCliPath()),
extraArgs: {
...userProvidedOptions?.extraArgs,
@@ -2422,7 +2765,6 @@ export class ClaudeAcpAgent implements Agent {
effortLevel: initialEffort.currentValue as Settings["effortLevel"],
});
}
this.sessions[sessionId] = {
query: q,
input: input,
@@ -2448,6 +2790,8 @@ export class ClaudeAcpAgent implements Agent {
contextWindowSize:
inferContextWindowFromModel(models.currentModelId) ?? DEFAULT_CONTEXT_WINDOW,
taskState,
toolUseCache: {},
messageIdToUuid: new Map(),
};
return {
@@ -2739,11 +3083,14 @@ function tokenizeModelPreference(model: string): { tokens: string[]; contextHint
function scoreModelMatch(model: ModelInfo, tokens: string[], contextHint?: string): number {
const haystack = `${model.value} ${model.displayName}`.toLowerCase();
let score = 0;
let nonHintMatched = false;
for (const token of tokens) {
if (haystack.includes(token)) {
if (token !== contextHint) nonHintMatched = true;
score += token === contextHint ? 3 : 1;
}
}
if (contextHint && !nonHintMatched) return 0;
return score;
}
@@ -2848,6 +3195,21 @@ function applyAvailableModelsAllowlist(sdkModels: ModelInfo[], allowlist: string
seen.add(trimmed);
}
// The custom model option (ANTHROPIC_CUSTOM_MODEL_OPTION) is exempt from the
// allowlist, the same way Default is. Per the model-config docs it adds an
// entry "without replacing the built-in aliases" and "appears at the bottom of
// the /model picker", so we append it last and skip the allowlist filter; this
// keeps a slim alias allowlist from hiding the custom model row.
// https://code.claude.com/docs/en/model-config#add-a-custom-model-option
const customModelOption = process.env.ANTHROPIC_CUSTOM_MODEL_OPTION?.trim();
if (customModelOption && !seen.has(customModelOption)) {
const customModel = sdkModels.find((m) => m.value === customModelOption);
if (customModel) {
result.push(customModel);
seen.add(customModel.value);
}
}
return result;
}
@@ -3039,6 +3401,54 @@ export function promptToClaude(prompt: PromptRequest): SDKUserMessage {
};
}
/**
* Resolves the ACP `messageId` for a Claude SDK message (live) or a persisted
* transcript message (replay) so chunk grouping is identical in both views.
*
* Assistant turns are keyed by the Anthropic API message id (`message.id`),
* which is identical at `message_start`, on the consolidated assistant message,
* and in the persisted transcript — unlike the per-`stream_event` uuid, which is
* unique per event and never persisted. User messages have no API id, but they
* are never streamed, so their (stable) SDK uuid is used instead. ACP message
* ids are opaque strings, so no particular format is required.
*/
export function messageIdForGrouping(message: {
type?: string;
uuid?: string | null;
message?: unknown;
}): string | undefined {
if (message.type === "assistant") {
const inner = message.message;
const apiId =
inner && typeof inner === "object" && "id" in inner
? (inner as { id?: unknown }).id
: undefined;
if (typeof apiId === "string" && apiId.length > 0) {
return apiId;
}
}
return typeof message.uuid === "string" && message.uuid.length > 0 ? message.uuid : undefined;
}
/**
* Stamps an ACP `messageId` onto a session update, but only on the message/
* thought chunk variants that carry one — tool_call/plan/etc. updates never do.
* No-op when `messageId` is falsy, so callers can pass it through unconditionally.
*/
function applyMessageId(
update: SessionNotification["update"],
messageId: string | undefined,
): void {
if (
messageId &&
(update.sessionUpdate === "agent_message_chunk" ||
update.sessionUpdate === "user_message_chunk" ||
update.sessionUpdate === "agent_thought_chunk")
) {
update.messageId = messageId;
}
}
/**
* Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
* Only handles text, image, and thinking chunks for now.
@@ -3056,6 +3466,12 @@ export function toAcpNotifications(
parentToolUseId?: string | null;
cwd?: string;
taskState?: TaskState;
// Opaque id identifying the message these chunks belong to (ACP message ids
// are opaque strings — no particular format is required). Attached to
// user/agent message and thought chunks so clients can group streamed chunks
// into a single message. Omit it (leave undefined) when unknown — never send
// an explicit `null`.
messageId?: string;
},
): SessionNotification[] {
const taskState = options?.taskState ?? new Map();
@@ -3069,6 +3485,7 @@ export function toAcpNotifications(
text: content,
},
};
applyMessageId(update, options?.messageId);
if (options?.parentToolUseId) {
update._meta = {
@@ -3144,42 +3561,41 @@ export function toAcpNotifications(
} else {
// Only register hooks on first encounter to avoid double-firing
if (registerHooks && !alreadyCached) {
// Capture the tool name in the closure rather than re-reading the
// cache when the hook fires. The cache entry is pruned at
// tool_result time, and a PostToolUse hook can fire after that, so
// closing over the name keeps the diff working without depending on
// (or pinning) the cache entry's lifetime.
const toolName = chunk.name;
registerHookCallback(chunk.id, {
onPostToolUseHook: async (toolUseId, toolInput, toolResponse) => {
const toolUse = toolUseCache[toolUseId];
if (toolUse) {
// Both `Edit` and `Write` produce a structuredPatch in their
// PostToolUse tool_response. For Edit the diff replaces the
// optimistic content built at tool_use time. For Write the
// optimistic content (built from `input.content` alone with
// `oldText: null`) shows "creation" semantics regardless of
// whether the file existed; the structuredPatch from the
// hook lets us emit the real diff for `type: "update"`. The
// helper returns `{}` if the response shape isn't usable.
const editDiff =
toolUse.name === "Edit" || toolUse.name === "Write"
? toolUpdateFromDiffToolResponse(toolResponse)
: {};
const update: SessionNotification["update"] = {
_meta: {
claudeCode: {
toolResponse,
toolName: toolUse.name,
},
} satisfies ToolUpdateMeta,
toolCallId: toolUseId,
sessionUpdate: "tool_call_update",
...editDiff,
};
await client.sessionUpdate({
sessionId,
update,
});
} else {
logger.error(
`[claude-agent-acp] Got a tool response for tool use that wasn't tracked: ${toolUseId}`,
);
}
// Both `Edit` and `Write` produce a structuredPatch in their
// PostToolUse tool_response. For Edit the diff replaces the
// optimistic content built at tool_use time. For Write the
// optimistic content (built from `input.content` alone with
// `oldText: null`) shows "creation" semantics regardless of
// whether the file existed; the structuredPatch from the
// hook lets us emit the real diff for `type: "update"`. The
// helper returns `{}` if the response shape isn't usable.
const editDiff =
toolName === "Edit" || toolName === "Write"
? toolUpdateFromDiffToolResponse(toolResponse)
: {};
const update: SessionNotification["update"] = {
_meta: {
claudeCode: {
toolResponse,
toolName,
},
} satisfies ToolUpdateMeta,
toolCallId: toolUseId,
sessionUpdate: "tool_call_update",
...editDiff,
};
await client.sessionUpdate({
sessionId,
update,
});
},
});
}
@@ -3316,6 +3732,11 @@ export function toAcpNotifications(
...toolUpdate,
};
}
// The tool_use is fully resolved now — drop it so a long session doesn't
// retain every tool call. The PostToolUse hook (Edit/Write diffs) closes
// over the tool name and no longer reads the cache, so pruning here is
// safe regardless of hook/result ordering.
delete toolUseCache[chunk.tool_use_id];
break;
}
@@ -3330,6 +3751,7 @@ export function toAcpNotifications(
case "compaction_delta":
case "advisor_tool_result":
case "mid_conv_system":
case "fallback":
break;
default:
@@ -3346,6 +3768,7 @@ export function toAcpNotifications(
},
};
}
applyMessageId(update, options?.messageId);
output.push({ sessionId, update });
}
}
@@ -3363,6 +3786,7 @@ export function streamEventToAcpNotifications(
clientCapabilities?: ClientCapabilities;
cwd?: string;
taskState?: TaskState;
messageId?: string;
},
): SessionNotification[] {
const event = message.event;
@@ -3380,6 +3804,7 @@ export function streamEventToAcpNotifications(
parentToolUseId: message.parent_tool_use_id,
cwd: options?.cwd,
taskState: options?.taskState,
messageId: options?.messageId,
},
);
case "content_block_delta":
@@ -3395,6 +3820,7 @@ export function streamEventToAcpNotifications(
parentToolUseId: message.parent_tool_use_id,
cwd: options?.cwd,
taskState: options?.taskState,
messageId: options?.messageId,
},
);
// No content. `ping` is a Messages-API keep-alive event that the SDK's
@@ -3436,7 +3862,8 @@ function commonPrefixLength(a: string, b: string) {
}
/** Best-effort first guess of a model's context window from its ID, used only
* until a `result` message arrives with the authoritative `modelUsage` value.
* as a fallback when the SDK's authoritative `getContextUsage` is unavailable
* (and until a `result` message arrives with the `modelUsage` value).
* Anthropic 1M-context variants encode "1m" as a distinct token in the SDK
* model ID (e.g., "claude-opus-4-6-1m"), which `\b1m\b` catches without also
* matching things like "10m" or embedded substrings. */
@@ -3445,6 +3872,45 @@ function inferContextWindowFromModel(model: string): number | null {
return null;
}
/** Fetch the SDK's authoritative context-window occupancy via the
* `getContextUsage` control request. Unlike the per-message API usage numbers
* (which only count message tokens), this `totalTokens` includes the system
* prompt, tool schemas, MCP tools, and memory-file overhead — the real
* occupancy the user sees. Returns `null` on any control-request failure.
*
* Note: we deliberately do NOT use this response's window fields for `size`.
* They have been observed to under-report extended (1M) context windows, so
* the window keeps coming from `modelUsage` / `inferContextWindowFromModel`,
* which handle the 1M variants correctly. */
async function fetchContextUsedTokens(query: Query, logger: Logger): Promise<number | null> {
try {
const usage = await query.getContextUsage();
return usage.totalTokens;
} catch (error) {
logger.error("Failed to fetch context usage from SDK:", error);
return null;
}
}
/** Translate the legacy `MAX_THINKING_TOKENS` env var into the SDK's `thinking`
* option. The `maxThinkingTokens` option it used to feed is deprecated and
* reduced to on/off on current models, so map the value to explicit thinking
* config instead: unset → `undefined` (SDK default, adaptive on models that
* support it); `0` → disabled; a positive integer → a fixed token budget.
* Anything else is ignored with a warning. */
function resolveThinkingConfig(
raw: string | undefined,
logger: Logger,
): ThinkingConfig | undefined {
if (raw === undefined) return undefined;
const parsed = Number.parseInt(raw, 10);
if (Number.isNaN(parsed) || parsed < 0) {
logger.error(`Ignoring MAX_THINKING_TOKENS: expected a non-negative integer, got '${raw}'.`);
return undefined;
}
return parsed === 0 ? { type: "disabled" } : { type: "enabled", budgetTokens: parsed };
}
function parseModelConfig(
raw: string | undefined,
): { modelOverrides?: Record<string, string>; availableModels?: string[] } | undefined {

249
src/elicitation.ts Normal file
View File

@@ -0,0 +1,249 @@
import { randomUUID } from "node:crypto";
import type {
CreateElicitationRequest,
CreateElicitationResponse,
ElicitationPropertySchema,
ElicitationSchema,
EnumOption,
} from "@agentclientprotocol/sdk";
import type { ElicitationRequest, ElicitationResult } from "@anthropic-ai/claude-agent-sdk";
import type {
AskUserQuestionInput,
AskUserQuestionOutput,
} from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
/**
* Bridges between the Claude Agent SDK's elicitation/dialog callbacks and ACP's
* (unstable) elicitation protocol.
*
* Two distinct SDK surfaces flow through here:
*
* 1. `onElicitation` — fired when an MCP server requests user input. These map
* directly onto ACP `session/create_elicitation` (form or url mode).
* 2. The built-in AskUserQuestion tool — when a `canUseTool` callback is
* registered the SDK routes its permission check through `canUseTool`
* (not the interactive `permission_ask_user_question` dialog). We render
* its questions as an ACP form elicitation and feed the user's selections
* back as the tool's `updatedInput`, which the tool's own `call()` reads.
*/
/** Modes the connected client advertised support for. */
export type ElicitationSupport = {
form: boolean;
url: boolean;
};
/**
* Convert an MCP elicitation request (from the SDK's `onElicitation` callback)
* into an ACP `CreateElicitationRequest`. Returns `null` when the request can't
* be represented (e.g. a url-mode request with no url).
*/
export function mcpElicitationToCreateRequest(
request: ElicitationRequest,
sessionId: string,
): CreateElicitationRequest | null {
if (request.mode === "url") {
if (!request.url) {
return null;
}
return {
mode: "url",
sessionId,
message: request.message,
url: request.url,
// URL elicitations need a stable id so the client can correlate the
// later `session/complete_elicitation` notification. MCP servers usually
// provide one; fall back to a generated id if not.
elicitationId: request.elicitationId ?? randomUUID(),
};
}
// Form mode (the default). The MCP `requestedSchema` is already a JSON Schema
// with primitive-typed properties, which is structurally what ACP expects.
return {
mode: "form",
sessionId,
message: request.message,
requestedSchema: normalizeElicitationSchema(request.requestedSchema),
};
}
/**
* Map an ACP elicitation response back to the MCP `ElicitResult` the SDK expects
* to hand back to the requesting server.
*/
export function createElicitationResponseToElicitResult(
response: CreateElicitationResponse,
): ElicitationResult {
switch (response.action) {
case "accept":
return { action: "accept", content: response.content ?? {} };
case "decline":
return { action: "decline" };
case "cancel":
default:
return { action: "cancel" };
}
}
/**
* A single question as supplied by the AskUserQuestion tool. Derived from the
* SDK's input type so the shape stays in sync; the SDK validates the model's
* tool call against this schema before it reaches us.
*/
export type AskUserQuestion = AskUserQuestionInput["questions"][number];
/**
* Pull the well-formed questions out of an AskUserQuestion tool input. Returns
* `null` when there are no usable questions — including the case where every
* entry is malformed and filtering leaves an empty list — so callers can treat
* "nothing to ask" uniformly.
*/
export function extractAskUserQuestions(input: Record<string, unknown>): AskUserQuestion[] | null {
const questions = (input as { questions?: unknown }).questions;
if (!Array.isArray(questions)) {
return null;
}
const valid = questions.filter(
(q): q is AskUserQuestion =>
!!q && typeof q.question === "string" && Array.isArray(q.options) && q.options.length > 0,
);
return valid.length > 0 ? valid : null;
}
/** Stable form-field key for the question at the given index. */
function questionFieldKey(index: number): string {
return `question_${index}`;
}
/** Form-field key for the optional free-text "custom answer" field. */
const CUSTOM_ANSWER_FIELD = "customAnswer";
/**
* Render the AskUserQuestion tool's questions as an ACP form elicitation.
*
* Fields are keyed by a short stable id (`question_<n>`) rather than the full
* question text, so the question text appears in exactly one place per field.
* Single-select questions use a titled `oneOf` enum; multi-select questions use
* an array with a titled `anyOf` item enum. The enum `const` is always the
* option label, since that is what the tool records as the answer.
*
* A trailing optional free-text field mirrors the CLI's custom-answer box: the
* user can type their own answer instead of (or as well as) picking an option.
* Nothing is marked required, so the user can also just skip — matching the
* built-in tool, which always offers Skip + a free-text box.
*/
export function askUserQuestionsToCreateRequest(
questions: AskUserQuestion[],
sessionId: string,
toolCallId: string | undefined,
): CreateElicitationRequest {
const single = questions.length === 1;
const properties: Record<string, ElicitationPropertySchema> = {};
questions.forEach((question, index) => {
const options: EnumOption[] = question.options.map((option) => ({
const: option.label,
title: option.description ? `${option.label} — ${option.description}` : option.label,
}));
// For a single question the prompt is carried by `message`, so we don't
// repeat it in the field description. With multiple questions each field
// needs its own question text.
const description = single ? undefined : question.question;
const title = question.header || undefined;
properties[questionFieldKey(index)] = question.multiSelect
? { type: "array", title, description, items: { anyOf: options } }
: { type: "string", title, description, oneOf: options };
});
properties[CUSTOM_ANSWER_FIELD] = {
type: "string",
title: "Other",
description: "Type your own answer instead of choosing an option above (optional).",
};
const requestedSchema: ElicitationSchema = {
type: "object",
properties,
};
const message = single ? questions[0].question : "Please answer the following questions.";
return {
mode: "form",
sessionId,
...(toolCallId ? { toolCallId } : {}),
message,
requestedSchema,
};
}
/** Outcome of an AskUserQuestion elicitation, decoupled from any transport. */
export type AskUserQuestionOutcome =
| { action: "answered"; updatedInput: Record<string, unknown> }
| { action: "cancel" };
/**
* Fold an ACP elicitation response into the AskUserQuestion tool's input.
*
* Selected labels are read back from the indexed form fields and written into
* `answers` as a `{ [questionText]: label }` map (comma-joining multi-selects)
* — the key shape the tool's own `call()` reads. Free text from the custom-
* answer field becomes the tool's top-level `response`. Decline yields empty
* answers (the model is told the user skipped rather than the turn aborting);
* cancel aborts the tool call.
*/
export function applyAskElicitationResponse(
response: CreateElicitationResponse,
toolInput: Record<string, unknown>,
questions: AskUserQuestion[],
): AskUserQuestionOutcome {
if (response.action === "cancel") {
return { action: "cancel" };
}
if (response.action === "decline") {
return { action: "answered", updatedInput: { ...toolInput, answers: {} } };
}
const content = response.content ?? {};
// Typed against the tool's own output schema so the answer/response shapes
// stay in sync with what the built-in tool's call() expects to read back.
const answers: AskUserQuestionOutput["answers"] = {};
questions.forEach((question, index) => {
const value = content[questionFieldKey(index)];
if (value === undefined || value === null) {
return;
}
const text = Array.isArray(value) ? value.join(", ") : String(value);
if (text === "") {
return;
}
answers[question.question] = text;
});
const updatedInput: Record<string, unknown> = { ...toolInput, answers };
const custom = content[CUSTOM_ANSWER_FIELD];
if (typeof custom === "string" && custom.trim() !== "") {
const response: AskUserQuestionOutput["response"] = custom;
updatedInput.response = response;
}
return { action: "answered", updatedInput };
}
/**
* Coerce an arbitrary MCP `requestedSchema` into an ACP `ElicitationSchema`.
* The two are structurally compatible JSON Schemas; we just guarantee the
* `type: "object"` discriminator is present.
*/
function normalizeElicitationSchema(
schema: Record<string, unknown> | undefined,
): ElicitationSchema {
if (!schema || typeof schema !== "object") {
return { type: "object", properties: {} };
}
return { ...(schema as ElicitationSchema), type: "object" };
}

View File

@@ -585,7 +585,7 @@ describe("ClaudeAcpAgent settings", () => {
);
});
it("does not inherit display info across mismatched model family versions", async () => {
it("does not inherit display info across mismatched model versions", async () => {
// https://github.com/agentclientprotocol/claude-agent-acp/issues/639:
// when the SDK's `opus` alias resolves to Opus 4.7, an allowlist entry
// of `claude-opus-4-6` (or `claude-opus-4-6[1m]`) used to substring-match
@@ -646,6 +646,172 @@ describe("ClaudeAcpAgent settings", () => {
expect(byValue["claude-opus-4-7"].name).toBe("Opus 4.7");
expect(byValue["claude-opus-4-7[1m]"].name).toMatch(/Opus 4\.7/);
});
it("does not inherit display info across mismatched model families", async () => {
// https://github.com/agentclientprotocol/claude-agent-acp/issues/639:
// when the SDK's `opus` alias resolves to Opus 4.8, `claude-opus-4-6[1m]`
// fails the version check against `opus` but the tokenized matcher
// falls through to `sonnet[1m]` because the `1m` context hint alone
// is enough to score a match.
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify({
availableModels: ["claude-opus-4-6", "claude-opus-4-6[1m]"],
}),
);
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
mockQueryWithModels([
{ value: "default", displayName: "Default", description: "Default model" },
{
value: "sonnet",
displayName: "Sonnet",
description: "Sonnet 4.6 · Best for everyday tasks",
},
{
value: "sonnet[1m]",
displayName: "Sonnet (1M context)",
description: "Sonnet 4.6 for long sessions",
},
{
value: "opus",
displayName: "Opus 4.8",
description: "Claude Opus 4.8",
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
const modelOption = response.configOptions.find((o: any) => o.id === "model");
const byValue: Record<string, { name: string; description?: string }> = {};
for (const opt of modelOption.options) {
byValue[opt.value] = { name: opt.name, description: opt.description };
}
expect(byValue["claude-opus-4-6[1m]"].name).toBe("claude-opus-4-6[1m]");
expect(byValue["claude-opus-4-6"].name).toBe("claude-opus-4-6");
});
it("preserves ANTHROPIC_CUSTOM_MODEL_OPTION even when absent from the allowlist", async () => {
// Per the model-config docs, ANTHROPIC_CUSTOM_MODEL_OPTION adds an entry
// "without replacing the built-in aliases" and "appears at the bottom of
// the /model picker", so it is exempt from the availableModels allowlist
// (the same way the Default option is "not affected by availableModels").
// ACP must match: a slim alias allowlist must not hide the custom model
// row, and it appears last, after the allowlisted entries.
// https://code.claude.com/docs/en/model-config#add-a-custom-model-option
const originalEnv = process.env.ANTHROPIC_CUSTOM_MODEL_OPTION;
process.env.ANTHROPIC_CUSTOM_MODEL_OPTION = "claude-opus-4-8[1m]";
try {
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify({
availableModels: ["sonnet", "opus", "haiku"],
}),
);
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
mockQueryWithModels([
{ value: "default", displayName: "Default", description: "Default model" },
{ value: "sonnet", displayName: "Sonnet", description: "Claude Sonnet 4.6" },
{ value: "opus", displayName: "Opus", description: "Claude Opus 4.6" },
{ value: "haiku", displayName: "Haiku", description: "Claude Haiku 4.5" },
{
value: "claude-opus-4-8[1m]",
displayName: "Opus 4.8",
description: "Claude Opus 4.8",
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
const modelOption = response.configOptions.find((o: any) => o.id === "model");
expect(modelOption.options.map((o: any) => o.value)).toEqual([
"default",
"sonnet",
"opus",
"haiku",
"claude-opus-4-8[1m]",
]);
const custom = modelOption.options.find((o: any) => o.value === "claude-opus-4-8[1m]");
expect(custom.name).toBe("Opus 4.8");
expect(custom.description).toBe("Claude Opus 4.8");
} finally {
if (originalEnv === undefined) {
delete process.env.ANTHROPIC_CUSTOM_MODEL_OPTION;
} else {
process.env.ANTHROPIC_CUSTOM_MODEL_OPTION = originalEnv;
}
}
});
it("does not duplicate the custom model option when also in the allowlist", async () => {
// If the user lists the custom model's exact ID in availableModels AND it
// is set as ANTHROPIC_CUSTOM_MODEL_OPTION, it must appear exactly once.
const originalEnv = process.env.ANTHROPIC_CUSTOM_MODEL_OPTION;
process.env.ANTHROPIC_CUSTOM_MODEL_OPTION = "claude-opus-4-8[1m]";
try {
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify({
availableModels: ["sonnet", "claude-opus-4-8[1m]"],
}),
);
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
mockQueryWithModels([
{ value: "default", displayName: "Default", description: "Default model" },
{ value: "sonnet", displayName: "Sonnet", description: "Claude Sonnet 4.6" },
{
value: "claude-opus-4-8[1m]",
displayName: "Opus 4.8",
description: "Claude Opus 4.8",
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
const modelOption = response.configOptions.find((o: any) => o.id === "model");
expect(modelOption.options.map((o: any) => o.value)).toEqual([
"default",
"sonnet",
"claude-opus-4-8[1m]",
]);
} finally {
if (originalEnv === undefined) {
delete process.env.ANTHROPIC_CUSTOM_MODEL_OPTION;
} else {
process.env.ANTHROPIC_CUSTOM_MODEL_OPTION = originalEnv;
}
}
});
});
it("resolves model aliases like opus[1m] to the correct model", async () => {

View File

@@ -6,6 +6,8 @@ import {
AvailableCommand,
Client,
ClientSideConnection,
CreateElicitationRequest,
CreateElicitationResponse,
ndJsonStream,
NewSessionResponse,
ReadTextFileRequest,
@@ -33,10 +35,16 @@ import {
claudeCliPath,
describeAlwaysAllow,
streamEventToAcpNotifications,
messageIdForGrouping,
type SDKMessageFilter,
} from "../acp-agent.js";
import { Pushable } from "../utils.js";
import { deleteSession, query, SDKAssistantMessage } from "@anthropic-ai/claude-agent-sdk";
import {
deleteSession,
getSessionMessages,
query,
SDKAssistantMessage,
} from "@anthropic-ai/claude-agent-sdk";
import { randomUUID } from "crypto";
vi.mock("@anthropic-ai/claude-agent-sdk", async (importOriginal) => {
@@ -83,6 +91,10 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
agent: Agent;
files: Map<string, string> = new Map();
receivedText: string = "";
// Records for the AskUserQuestion elicitation test.
elicitations: CreateElicitationRequest[] = [];
permissionToolInputs: unknown[] = [];
chosenAnswers: Record<string, string | string[]> = {};
resolveAvailableCommands: (commands: AvailableCommand[]) => void;
availableCommandsPromise: Promise<AvailableCommand[]>;
@@ -101,11 +113,39 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
}
async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {
// Record what asked for permission so a test can assert that
// AskUserQuestion did NOT fall back to a generic permission prompt.
this.permissionToolInputs.push(params.toolCall?.rawInput);
const optionId = params.options.find((p) => p.kind === "allow_once")!.optionId;
return { outcome: { outcome: "selected", optionId } };
}
async unstable_createElicitation(
params: CreateElicitationRequest,
): Promise<CreateElicitationResponse> {
this.elicitations.push(params);
if (params.mode !== "form") {
return { action: "decline" };
}
// Accept the first option of every choice field (skip the free-text one).
const content: Record<string, string | string[]> = {};
for (const [key, prop] of Object.entries(params.requestedSchema.properties ?? {})) {
if (key === "customAnswer") continue;
const p = prop as {
oneOf?: Array<{ const: string }>;
items?: { anyOf?: Array<{ const: string }> };
};
if (p.oneOf?.length) {
content[key] = p.oneOf[0].const;
} else if (p.items?.anyOf?.length) {
content[key] = [p.items.anyOf[0].const];
}
}
this.chosenAnswers = content;
return { action: "accept", content };
}
async sessionUpdate(params: SessionNotification): Promise<void> {
console.error("RECEIVED", JSON.stringify(params, null, 4));
@@ -158,6 +198,9 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
readTextFile: true,
writeTextFile: true,
},
elicitation: {
form: {},
},
},
});
@@ -170,7 +213,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
}
it("should connect to the ACP subprocess", async () => {
const { client, connection, newSessionResponse } = await setupTestSession("./");
const { client, connection, newSessionResponse } = await setupTestSession(process.cwd());
await connection.prompt({
prompt: [
@@ -261,6 +304,60 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
expect(client.takeReceivedText()).toContain("Compacting...\n\nCompacting completed.");
}, 60000);
// Regression guard for the SDK's AskUserQuestion routing. The built-in
// AskUserQuestion tool is delivered to us through `canUseTool` (not the
// interactive `onUserDialog` path), where we intercept it and render an ACP
// form elicitation, returning the answer via `updatedInput`. If a future SDK
// changes that routing — e.g. stops calling `canUseTool` for it, or no longer
// reads answers back from `updatedInput` — this test fails: either no
// elicitation arrives, the tool falls back to a permission prompt, or the
// answer never reaches the model's reply.
it("routes AskUserQuestion through ACP form elicitation and round-trips the answer", async () => {
const { client, connection, newSessionResponse } = await setupTestSession(process.cwd());
await connection.prompt({
prompt: [
{
type: "text",
text:
"Use the AskUserQuestion tool right now to ask me to choose a favorite color. " +
"Offer exactly two options: 'Red' and 'Blue'. Do not use any other tool and do " +
"not ask in plain text. After I answer, reply with one short sentence naming the " +
"color I picked.",
},
],
sessionId: newSessionResponse.sessionId,
});
// The tool surfaced as an ACP form elicitation...
expect(client.elicitations.length).toBeGreaterThan(0);
const elicitation = client.elicitations[0];
expect(elicitation.mode).toBe("form");
// ...built by our converter (indexed field key + free-text "Other" field),
// which confirms our interception path produced it rather than some other
// mechanism.
const properties =
elicitation.mode === "form" ? Object.keys(elicitation.requestedSchema.properties ?? {}) : [];
expect(properties).toContain("question_0");
expect(properties).toContain("customAnswer");
// AskUserQuestion must NOT fall back to a generic permission prompt: no
// permission request should have carried AskUserQuestion's `questions`.
const fellBackToPermission = client.permissionToolInputs.some(
(input) =>
!!input &&
typeof input === "object" &&
Array.isArray((input as { questions?: unknown }).questions),
);
expect(fellBackToPermission).toBe(false);
// The chosen answer round-trips: the model's reply names the picked option.
const picked = String(Object.values(client.chosenAnswers)[0] ?? "");
expect(picked).not.toEqual("");
expect(client.takeReceivedText().toLowerCase()).toContain(picked.toLowerCase());
}, 60000);
});
describe("tool conversions", () => {
@@ -1318,6 +1415,67 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("SDK behavior", () => {
const { value } = await q.next();
expect(value).toMatchObject({ type: "system", session_id: sessionId });
}, 10000);
// Pins the SDK invariant our `messageId` plumbing relies on: the Anthropic
// API message id is available at `message_start` (before any delta), is the
// same on the consolidated assistant message, and is recoverable from the
// persisted transcript — so a turn keeps one stable id across streaming and
// replay. The per-`stream_event` uuid is NOT used because it is unique per
// event and never persisted; this test would fail if a future SDK regressed
// any of those properties.
it("uses the API message id as a stable anchor across streaming and replay", async () => {
const sessionId = randomUUID();
const q = query({
prompt: "Reply with exactly these words and nothing else: hello there my friend",
options: {
systemPrompt: { type: "preset", preset: "claude_code" },
sessionId,
includePartialMessages: true,
maxTurns: 1,
allowedTools: [],
},
});
let messageStartApiId: string | undefined;
let consolidatedApiId: string | undefined;
let sawDelta = false;
let allPartialsTopLevel = true;
for await (const message of q) {
if (message.type === "assistant") {
consolidatedApiId = message.message.id;
}
if (message.type !== "stream_event") continue;
// Every streaming partial must belong to the top-level agent
// (parent_tool_use_id === null). Subagent work is folded into tool-result
// messages rather than surfaced as partial streams, which is what lets us
// track a single anchor without keying by parent_tool_use_id.
if (message.parent_tool_use_id !== null) allPartialsTopLevel = false;
if (message.event.type === "message_start") {
messageStartApiId = message.event.message.id;
} else if (message.event.type === "content_block_delta") {
sawDelta = true;
}
}
// The API message id is present at message_start (before deltas), so we can
// tag every streamed chunk with it, and it is identical on the consolidated
// assistant message.
expect(messageStartApiId).toBeTruthy();
expect(sawDelta).toBe(true);
expect(allPartialsTopLevel).toBe(true);
expect(consolidatedApiId).toBe(messageStartApiId);
// ...and the SAME id is recoverable from the persisted transcript, so chunks
// grouped live keep their id when the session is replayed.
const persisted = await getSessionMessages(sessionId);
const replayedAssistant = persisted.find((m) => m.type === "assistant");
expect(replayedAssistant).toBeDefined();
expect((replayedAssistant!.message as { id?: string }).id).toBe(messageStartApiId);
// The helper used in production must derive that same id from the replayed
// message.
expect(messageIdForGrouping(replayedAssistant!)).toBe(messageStartApiId);
}, 30000);
});
describe("permission requests", () => {
@@ -1548,6 +1706,8 @@ describe("stop reason propagation", () => {
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
}
@@ -1692,6 +1852,8 @@ describe("stop reason propagation", () => {
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
const response = await agent.prompt({
@@ -1851,6 +2013,8 @@ describe("session/close", () => {
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
return agent.sessions[sessionId]!;
}
@@ -1935,6 +2099,8 @@ describe("session/delete", () => {
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
return agent.sessions[sessionId]!;
}
@@ -1948,7 +2114,7 @@ describe("session/delete", () => {
const agent = createMockAgent();
const session = injectSession(agent, "session-1");
const result = await agent.unstable_deleteSession({ sessionId: "session-1" });
const result = await agent.deleteSession({ sessionId: "session-1" });
expect(result).toEqual({});
expect(agent.sessions["session-1"]).toBeUndefined();
@@ -1961,7 +2127,7 @@ describe("session/delete", () => {
it("deletes a session from disk that is not currently active", async () => {
const agent = createMockAgent();
const result = await agent.unstable_deleteSession({ sessionId: "not-active" });
const result = await agent.deleteSession({ sessionId: "not-active" });
expect(result).toEqual({});
expect(deleteSession).toHaveBeenCalledWith("not-active");
@@ -1971,7 +2137,7 @@ describe("session/delete", () => {
const agent = createMockAgent();
vi.mocked(deleteSession).mockRejectedValueOnce(new Error("Session not found on disk"));
await expect(agent.unstable_deleteSession({ sessionId: "missing" })).rejects.toThrow(
await expect(agent.deleteSession({ sessionId: "missing" })).rejects.toThrow(
"Session not found on disk",
);
});
@@ -1981,7 +2147,7 @@ describe("session/delete", () => {
injectSession(agent, "session-a");
injectSession(agent, "session-b");
await agent.unstable_deleteSession({ sessionId: "session-a" });
await agent.deleteSession({ sessionId: "session-a" });
expect(agent.sessions["session-a"]).toBeUndefined();
expect(agent.sessions["session-b"]).toBeDefined();
@@ -2036,6 +2202,8 @@ describe("getOrCreateSession param change detection", () => {
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
return agent.sessions[sessionId]!;
}
@@ -2271,6 +2439,8 @@ describe("usage_update computation", () => {
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
}
@@ -3108,6 +3278,309 @@ describe("usage_update computation", () => {
// size should be 1000000 (Opus), not 200000 (the fallback if <synthetic> overrode the model)
expect(usageUpdate.update.size).toBe(1000000);
});
it("compact_boundary uses authoritative getContextUsage for used, keeps session window for size", async () => {
const { agent, updates } = createMockAgentWithCapture();
injectSession(agent, [
{ type: "system", subtype: "compact_boundary", session_id: "test-session" },
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
const session = agent.sessions["test-session"];
// A 1M window learned earlier (e.g. from modelUsage) must survive compaction
// — getContextUsage's window field under-reports it, so we don't use it.
session.contextWindowSize = 1000000;
(session.query as any).getContextUsage = vi
.fn()
.mockResolvedValue({ totalTokens: 12345, rawMaxTokens: 200000 });
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
const usageUpdate = updates.find((u: any) => u.update?.sessionUpdate === "usage_update");
expect(usageUpdate).toBeDefined();
expect(usageUpdate.update.used).toBe(12345);
// size stays at the session's learned window, NOT getContextUsage's value.
expect(usageUpdate.update.size).toBe(1000000);
expect(session.contextWindowSize).toBe(1000000);
});
it("compact_boundary falls back to used:0 when getContextUsage fails", async () => {
const { agent, updates } = createMockAgentWithCapture();
injectSession(agent, [
{ type: "system", subtype: "compact_boundary", session_id: "test-session" },
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
const session = agent.sessions["test-session"];
session.contextWindowSize = 200000;
(session.query as any).getContextUsage = vi.fn().mockRejectedValue(new Error("boom"));
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
const usageUpdate = updates.find((u: any) => u.update?.sessionUpdate === "usage_update");
expect(usageUpdate).toBeDefined();
expect(usageUpdate.update.used).toBe(0);
expect(usageUpdate.update.size).toBe(200000);
expect(session.contextWindowSize).toBe(200000);
});
});
describe("assembled assistant text fallback", () => {
const ZERO_USAGE = {
input_tokens: 0,
output_tokens: 0,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
};
function createMockAgentWithCapture() {
const updates: any[] = [];
const mockClient = {
sessionUpdate: async (notification: any) => {
updates.push(notification);
},
} as unknown as AgentSideConnection;
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
return { agent, updates };
}
function messageStart(apiId: string) {
return {
type: "stream_event" as const,
parent_tool_use_id: null,
uuid: randomUUID(),
session_id: "test-session",
event: {
type: "message_start" as const,
message: { id: apiId, model: "claude-sonnet-4-20250514", usage: ZERO_USAGE },
},
};
}
function textDelta(text: string) {
return {
type: "stream_event" as const,
parent_tool_use_id: null,
uuid: randomUUID(),
session_id: "test-session",
event: {
type: "content_block_delta" as const,
index: 0,
delta: { type: "text_delta" as const, text },
},
};
}
function assistantMessage(apiId: string, content: any[], parentToolUseId: string | null = null) {
return {
type: "assistant" as const,
parent_tool_use_id: parentToolUseId,
uuid: randomUUID(),
session_id: "test-session",
message: {
id: apiId,
role: "assistant" as const,
model: "claude-sonnet-4-20250514",
content,
usage: ZERO_USAGE,
},
};
}
function result() {
return {
type: "result" as const,
subtype: "success" as const,
stop_reason: "end_turn",
is_error: false,
result: "",
errors: [],
duration_ms: 0,
duration_api_ms: 0,
num_turns: 1,
total_cost_usd: 0,
usage: ZERO_USAGE,
modelUsage: {},
permission_denials: [],
uuid: randomUUID(),
session_id: "test-session",
};
}
const idle = { type: "system", subtype: "session_state_changed", state: "idle" };
function injectSession(agent: ClaudeAcpAgent, messages: any[]) {
const input = new Pushable<any>();
async function* messageGenerator() {
const iter = input[Symbol.asyncIterator]();
const { value: userMessage, done } = await iter.next();
if (!done && userMessage) {
yield {
type: "user",
message: userMessage.message,
parent_tool_use_id: null,
uuid: userMessage.uuid,
session_id: "test-session",
isReplay: true,
};
}
yield* messages;
}
agent.sessions["test-session"] = {
query: messageGenerator() as any,
input,
cancelled: false,
cwd: "/test",
sessionFingerprint: JSON.stringify({ cwd: "/test", mcpServers: [] }),
modes: { currentModeId: "default", availableModes: [] },
models: { currentModelId: "default", availableModels: [] },
modelInfos: [],
settingsManager: { dispose: vi.fn() } as any,
accumulatedUsage: {
inputTokens: 0,
outputTokens: 0,
cachedReadTokens: 0,
cachedWriteTokens: 0,
},
configOptions: [],
promptRunning: false,
pendingMessages: new Map(),
nextPendingOrder: 0,
abortController: new AbortController(),
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
}
function messageChunkTexts(updates: any[]): string[] {
return updates
.filter((u) => u.update?.sessionUpdate === "agent_message_chunk")
.map((u) => u.update.content.text);
}
function thoughtChunkTexts(updates: any[]): string[] {
return updates
.filter((u) => u.update?.sessionUpdate === "agent_thought_chunk")
.map((u) => u.update.content.text);
}
it("emits the assembled text when no content_block_delta was streamed", async () => {
const { agent, updates } = createMockAgentWithCapture();
// Gateway delivers a fully assembled message with no preceding deltas.
injectSession(agent, [
assistantMessage("msg-no-stream", [{ type: "text", text: "the final answer" }]),
result(),
idle,
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
expect(messageChunkTexts(updates)).toEqual(["the final answer"]);
});
it("does not re-emit text already streamed via content_block_delta", async () => {
const { agent, updates } = createMockAgentWithCapture();
// Normal streaming: deltas arrive, then the consolidated message repeats them.
injectSession(agent, [
messageStart("msg-streamed"),
textDelta("hello "),
textDelta("world"),
assistantMessage("msg-streamed", [{ type: "text", text: "hello world" }]),
result(),
idle,
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
// Only the two streamed deltas — the assembled block is filtered out.
expect(messageChunkTexts(updates)).toEqual(["hello ", "world"]);
});
it("dedupes per block type: streamed text is dropped but an un-streamed thinking block in the same message is forwarded", async () => {
const { agent, updates } = createMockAgentWithCapture();
// Gateway streams the text live but delivers the thinking block only in the
// assembled message (no thinking_delta). The dedupe must be per-type so the
// thinking survives. This also makes the test non-vacuous: if the fallback
// were removed (text/thinking always dropped) the thought chunk disappears.
injectSession(agent, [
messageStart("msg-mixed"),
textDelta("streamed text"),
assistantMessage("msg-mixed", [
{ type: "text", text: "streamed text" },
{ type: "thinking", thinking: "private reasoning" },
]),
result(),
idle,
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
// Streamed text appears once (delta only — assembled copy deduped).
expect(messageChunkTexts(updates)).toEqual(["streamed text"]);
// The un-streamed thinking block is forwarded despite text having streamed.
expect(thoughtChunkTexts(updates)).toEqual(["private reasoning"]);
});
it("does not leak subagent assistant text into the top-level feed", async () => {
const { agent, updates } = createMockAgentWithCapture();
// Subagent assistant messages (parent_tool_use_id !== null) are never
// streamed live; their text/thinking is internal to the tool call and must
// stay filtered out, not surface as a fallback chunk.
injectSession(agent, [
assistantMessage(
"msg-subagent",
[{ type: "text", text: "subagent internal prose" }],
"tool_use_1",
),
result(),
idle,
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
expect(messageChunkTexts(updates)).toEqual([]);
expect(thoughtChunkTexts(updates)).toEqual([]);
});
it("forwards distinct blocks that a gateway splits across same-id messages", async () => {
const { agent, updates } = createMockAgentWithCapture();
// Observed with OpenAI-compatible gateways: one response id split into an
// empty thinking block, then the real text — both with no deltas.
injectSession(agent, [
assistantMessage("msg-split", [{ type: "thinking", thinking: "" }]),
assistantMessage("msg-split", [{ type: "text", text: "the real answer" }]),
result(),
idle,
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
// The text survives even though an earlier same-id message already triggered
// the fallback for a different (thinking) block.
expect(messageChunkTexts(updates)).toEqual(["the real answer"]);
// The empty thinking block carries nothing and must not produce a stray
// empty thought chunk.
expect(thoughtChunkTexts(updates)).toEqual([]);
});
it("re-forwards a block a gateway re-delivers (no content-keyed dedupe)", async () => {
const { agent, updates } = createMockAgentWithCapture();
// The fallback intentionally keys only on whether the id streamed live, not
// on block content — so a gateway re-delivering the same assembled block
// emits it twice. This is the accepted, cosmetic tradeoff for not caching
// every fallback block's full text; see `streamedTextMessageIds`.
injectSession(agent, [
assistantMessage("msg-dup", [{ type: "text", text: "answer" }]),
assistantMessage("msg-dup", [{ type: "text", text: "answer" }]),
result(),
idle,
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
expect(messageChunkTexts(updates)).toEqual(["answer", "answer"]);
});
});
describe("emitRawSDKMessages", () => {
@@ -3171,6 +3644,8 @@ describe("emitRawSDKMessages", () => {
emitRawSDKMessages,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
}
@@ -3399,6 +3874,8 @@ describe("result origin handling", () => {
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
}
@@ -3574,6 +4051,8 @@ describe("memory_recall handling", () => {
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
}
@@ -3804,6 +4283,8 @@ describe("post-error recovery", () => {
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
return { interrupt };
}
@@ -3949,6 +4430,8 @@ describe("session/cancel wedge recovery (issue #680)", () => {
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
toolUseCache: {},
messageIdToUuid: new Map(),
};
return { interrupt };
}
@@ -4088,4 +4571,166 @@ describe("streamEventToAcpNotifications", () => {
expect(result).toEqual([]);
expect(errors).toEqual([]);
});
it("attaches the supplied messageId to streamed text chunks", () => {
const messageId = randomUUID();
const message = {
type: "stream_event",
parent_tool_use_id: null,
uuid: randomUUID(),
session_id: "test-session",
event: {
type: "content_block_delta",
index: 0,
delta: { type: "text_delta", text: "hello" },
},
} as Parameters<typeof streamEventToAcpNotifications>[0];
const result = streamEventToAcpNotifications(
message,
"test",
{},
{} as AgentSideConnection,
console,
{ messageId },
);
expect(result).toEqual([
{
sessionId: "test",
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: "hello" },
messageId,
},
},
]);
});
});
describe("toAcpNotifications messageId", () => {
const messageId = "11111111-2222-3333-4444-555555555555";
it("sets messageId on agent message chunks from string content", () => {
const result = toAcpNotifications(
"hello world",
"assistant",
"test",
{},
{} as AgentSideConnection,
console,
{ messageId },
);
expect(result).toEqual([
{
sessionId: "test",
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: "hello world" },
messageId,
},
},
]);
});
it("sets messageId on user message chunks and thought chunks", () => {
const userResult = toAcpNotifications(
[{ type: "text", text: "hi" }],
"user",
"test",
{},
{} as AgentSideConnection,
console,
{ messageId },
);
expect(userResult[0].update).toMatchObject({
sessionUpdate: "user_message_chunk",
messageId,
});
const thoughtResult = toAcpNotifications(
[{ type: "thinking", thinking: "hmm", signature: "" }],
"assistant",
"test",
{},
{} as AgentSideConnection,
console,
{ messageId },
);
expect(thoughtResult[0].update).toMatchObject({
sessionUpdate: "agent_thought_chunk",
messageId,
});
});
it("omits messageId when none is supplied", () => {
const result = toAcpNotifications(
"hello",
"assistant",
"test",
{},
{} as AgentSideConnection,
console,
);
expect(result[0].update).not.toHaveProperty("messageId");
});
it("never sets messageId on non-chunk updates (tool_call)", () => {
const result = toAcpNotifications(
[
{
type: "tool_use",
id: "toolu_abc",
name: "Read",
input: { file_path: "/tmp/x" },
},
],
"assistant",
"test",
{},
{} as AgentSideConnection,
console,
{ messageId, registerHooks: false },
);
expect(result[0].update.sessionUpdate).toBe("tool_call");
expect(result[0].update).not.toHaveProperty("messageId");
});
});
describe("messageIdForGrouping", () => {
it("uses the Anthropic API message id for assistant messages", () => {
const message = {
type: "assistant",
uuid: "de242400-cdb3-4af7-9856-d3b114b20af9",
message: { id: "msg_018DQGVuZbGYwVnvDakAP9Do", role: "assistant" },
};
// The API id is identical at message_start, on the consolidated message,
// and in the persisted transcript — so it stays stable across replay,
// unlike the per-message uuid.
expect(messageIdForGrouping(message)).toBe("msg_018DQGVuZbGYwVnvDakAP9Do");
});
it("falls back to the uuid for assistant messages without an API id", () => {
const message = {
type: "assistant",
uuid: "de242400-cdb3-4af7-9856-d3b114b20af9",
message: { role: "assistant" },
};
expect(messageIdForGrouping(message)).toBe("de242400-cdb3-4af7-9856-d3b114b20af9");
});
it("uses the uuid for user messages (they carry no API id and aren't streamed)", () => {
const message = {
type: "user",
uuid: "11111111-2222-3333-4444-555555555555",
message: { id: "msg_should_be_ignored", role: "user" },
};
expect(messageIdForGrouping(message)).toBe("11111111-2222-3333-4444-555555555555");
});
it("returns undefined when there is no usable id", () => {
expect(messageIdForGrouping({ type: "system", message: {} })).toBeUndefined();
expect(messageIdForGrouping({ type: "assistant", uuid: "", message: {} })).toBeUndefined();
});
});

View File

@@ -39,7 +39,7 @@ vi.mock("../tools.js", async () => ({
describe("additionalRoots", () => {
let agent: ClaudeAcpAgentType;
const tempDirs: string[] = [];
const newSession = (meta: Record<string, unknown>, cwd = "/test") =>
const newSession = (meta: Record<string, unknown>, cwd = process.cwd()) =>
agent.newSession({ cwd, mcpServers: [], _meta: meta });
beforeEach(async () => {
@@ -79,7 +79,7 @@ describe("additionalRoots", () => {
it("prefers the official ACP additionalDirectories field over _meta.additionalRoots", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
additionalDirectories: ["/from/official"],
_meta: { additionalRoots: ["/from/meta"] },
@@ -89,7 +89,7 @@ describe("additionalRoots", () => {
it("merges official ACP additionalDirectories with claudeCode SDK additionalDirectories", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
additionalDirectories: ["/from/official"],
_meta: { claudeCode: { options: { additionalDirectories: ["/from/sdk"] } } },
@@ -99,7 +99,7 @@ describe("additionalRoots", () => {
it("falls back to _meta.additionalRoots when the official field is omitted", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: { additionalRoots: ["/from/meta"] },
});

View File

@@ -103,7 +103,7 @@ describe("authorization", () => {
});
await agent.newSession({
cwd: "testRoot",
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
@@ -148,7 +148,7 @@ describe("authorization", () => {
});
await agent.newSession({
cwd: "testRoot",
cwd: process.cwd(),
mcpServers: [],
});

View File

@@ -1,7 +1,10 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { AgentSideConnection, SessionNotification } from "@agentclientprotocol/sdk";
import { AgentSideConnection, RequestError, SessionNotification } from "@agentclientprotocol/sdk";
import type { Options } from "@anthropic-ai/claude-agent-sdk";
import type { ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
let capturedOptions: Options | undefined;
vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
@@ -65,7 +68,7 @@ describe("createSession options merging", () => {
it("merges user-provided disallowedTools with ACP internal list", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
@@ -85,7 +88,7 @@ describe("createSession options merging", () => {
it("works when user provides no disallowedTools", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
});
@@ -94,7 +97,7 @@ describe("createSession options merging", () => {
it("works when user provides empty disallowedTools", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
@@ -110,7 +113,7 @@ describe("createSession options merging", () => {
it("sets tools to empty array when disableBuiltInTools is true", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
disableBuiltInTools: true,
@@ -133,7 +136,7 @@ describe("createSession options merging", () => {
const userPreToolUseHook = { hooks: [{ command: "echo pre" }] };
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
@@ -155,7 +158,7 @@ describe("createSession options merging", () => {
it("inherits HOME and PATH from process.env when no env is provided", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
});
@@ -165,7 +168,7 @@ describe("createSession options merging", () => {
it("merges user-provided env vars on top of process.env", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
@@ -185,7 +188,7 @@ describe("createSession options merging", () => {
it("allows user-provided env vars to override process.env entries", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
@@ -203,7 +206,7 @@ describe("createSession options merging", () => {
it("defaults tools to claude_code preset when not provided", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
});
@@ -212,7 +215,7 @@ describe("createSession options merging", () => {
it("passes through user-provided tools string array", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
@@ -228,7 +231,7 @@ describe("createSession options merging", () => {
it("explicit tools array takes precedence over disableBuiltInTools", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
disableBuiltInTools: true,
@@ -245,7 +248,7 @@ describe("createSession options merging", () => {
it("passes through empty tools array to disable all built-in tools", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
@@ -261,7 +264,7 @@ describe("createSession options merging", () => {
describe("systemPrompt via _meta", () => {
it("defaults to the claude_code preset when not provided", async () => {
await agent.newSession({ cwd: "/test", mcpServers: [] });
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.systemPrompt).toEqual({
type: "preset",
@@ -271,7 +274,7 @@ describe("createSession options merging", () => {
it("replaces the preset when a string is provided", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: { systemPrompt: "custom prompt" },
});
@@ -281,7 +284,7 @@ describe("createSession options merging", () => {
it("forwards append", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: { systemPrompt: { append: "extra instructions" } },
});
@@ -295,7 +298,7 @@ describe("createSession options merging", () => {
it("forwards excludeDynamicSections", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: { systemPrompt: { excludeDynamicSections: true } },
});
@@ -309,7 +312,7 @@ describe("createSession options merging", () => {
it("forwards append and excludeDynamicSections together", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
systemPrompt: {
@@ -329,7 +332,7 @@ describe("createSession options merging", () => {
it("ignores caller-provided type/preset overrides", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
systemPrompt: {
@@ -369,7 +372,7 @@ describe("createSession options merging", () => {
modelOverrides: { "claude-opus-4-6": "us.anthropic.claude-opus-4-6-v1" },
});
await agent.newSession({ cwd: "/test", mcpServers: [] });
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.settings).toEqual({
modelOverrides: { "claude-opus-4-6": "us.anthropic.claude-opus-4-6-v1" },
@@ -381,7 +384,7 @@ describe("createSession options merging", () => {
availableModels: ["opus", "sonnet"],
});
await agent.newSession({ cwd: "/test", mcpServers: [] });
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.settings).toEqual({
availableModels: ["opus", "sonnet"],
@@ -394,7 +397,7 @@ describe("createSession options merging", () => {
availableModels: ["opus"],
});
await agent.newSession({ cwd: "/test", mcpServers: [] });
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.settings).toEqual({
modelOverrides: { "claude-opus-4-6": "us.anthropic.claude-opus-4-6-v1" },
@@ -403,7 +406,7 @@ describe("createSession options merging", () => {
});
it("does not add settings when env var is not set", async () => {
await agent.newSession({ cwd: "/test", mcpServers: [] });
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.settings).toBeUndefined();
});
@@ -414,7 +417,7 @@ describe("createSession options merging", () => {
});
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
@@ -438,13 +441,13 @@ describe("createSession options merging", () => {
it("throws on invalid JSON", async () => {
process.env.CLAUDE_MODEL_CONFIG = "not-json";
await expect(agent.newSession({ cwd: "/test", mcpServers: [] })).rejects.toThrow();
await expect(agent.newSession({ cwd: process.cwd(), mcpServers: [] })).rejects.toThrow();
});
});
it("merges user-provided mcpServers with ACP mcpServers", async () => {
await agent.newSession({
cwd: "/test",
cwd: process.cwd(),
mcpServers: [
{
name: "acp-server",
@@ -473,4 +476,140 @@ describe("createSession options merging", () => {
// ACP-provided MCP server should also be present
expect(capturedOptions!.mcpServers).toHaveProperty("acp-server");
});
describe("thinking config from MAX_THINKING_TOKENS", () => {
let originalMaxThinking: string | undefined;
beforeEach(() => {
originalMaxThinking = process.env.MAX_THINKING_TOKENS;
delete process.env.MAX_THINKING_TOKENS;
});
afterEach(() => {
if (originalMaxThinking !== undefined) {
process.env.MAX_THINKING_TOKENS = originalMaxThinking;
} else {
delete process.env.MAX_THINKING_TOKENS;
}
});
it("leaves thinking unset (SDK default) when env var is absent", async () => {
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.thinking).toBeUndefined();
// The deprecated option must not be set either.
expect(capturedOptions!.maxThinkingTokens).toBeUndefined();
});
it("maps 0 to disabled thinking", async () => {
process.env.MAX_THINKING_TOKENS = "0";
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.thinking).toEqual({ type: "disabled" });
});
it("maps a positive value to a fixed thinking budget", async () => {
process.env.MAX_THINKING_TOKENS = "12000";
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.thinking).toEqual({ type: "enabled", budgetTokens: 12000 });
});
it("ignores a non-numeric value", async () => {
process.env.MAX_THINKING_TOKENS = "lots";
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.thinking).toBeUndefined();
});
it("lets a user-provided thinking option override the env default", async () => {
process.env.MAX_THINKING_TOKENS = "12000";
await agent.newSession({
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
options: {
thinking: { type: "adaptive" },
},
},
},
});
expect(capturedOptions!.thinking).toEqual({ type: "adaptive" });
});
});
describe("cwd validation", () => {
it("rejects a relative cwd with invalidParams", async () => {
await expect(
agent.newSession({ cwd: "relative/path", mcpServers: [] }),
).rejects.toMatchObject({ code: RequestError.invalidParams().code });
});
it("rejects a non-existent cwd with invalidParams", async () => {
const missing = path.join(os.tmpdir(), "claude-acp-does-not-exist-xyz");
await expect(agent.newSession({ cwd: missing, mcpServers: [] })).rejects.toMatchObject({
code: RequestError.invalidParams().code,
});
});
it("rejects a cwd that points at a file with invalidParams", async () => {
const file = path.join(os.tmpdir(), "claude-acp-cwd-is-a-file.txt");
fs.writeFileSync(file, "not a directory");
try {
await expect(agent.newSession({ cwd: file, mcpServers: [] })).rejects.toMatchObject({
code: RequestError.invalidParams().code,
});
} finally {
fs.rmSync(file, { force: true });
}
});
it("accepts an existing absolute directory", async () => {
await expect(agent.newSession({ cwd: process.cwd(), mcpServers: [] })).resolves.toBeDefined();
});
});
describe("elicitation", () => {
it("keeps AskUserQuestion disabled and omits callbacks without elicitation capability", async () => {
await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.disallowedTools).toContain("AskUserQuestion");
expect(capturedOptions!.onElicitation).toBeUndefined();
});
it("enables AskUserQuestion and wires the elicitation callback when form is supported", async () => {
await agent.initialize({
protocolVersion: 1,
clientCapabilities: { elicitation: { form: {} } },
});
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.disallowedTools).not.toContain("AskUserQuestion");
expect(typeof capturedOptions!.onElicitation).toBe("function");
});
it("wires callbacks for url-only elicitation but keeps AskUserQuestion disabled", async () => {
await agent.initialize({
protocolVersion: 1,
clientCapabilities: { elicitation: { url: {} } },
});
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(capturedOptions!.disallowedTools).toContain("AskUserQuestion");
expect(typeof capturedOptions!.onElicitation).toBe("function");
});
it("still merges user-provided disallowedTools when AskUserQuestion is enabled", async () => {
await agent.initialize({
protocolVersion: 1,
clientCapabilities: { elicitation: { form: {} } },
});
await agent.newSession({
cwd: process.cwd(),
mcpServers: [],
_meta: { claudeCode: { options: { disallowedTools: ["WebSearch"] } } },
});
expect(capturedOptions!.disallowedTools).toContain("WebSearch");
expect(capturedOptions!.disallowedTools).not.toContain("AskUserQuestion");
});
});
});

View File

@@ -0,0 +1,309 @@
import { describe, it, expect } from "vitest";
import type { CreateElicitationRequest, CreateElicitationResponse } from "@agentclientprotocol/sdk";
import type { ElicitationRequest } from "@anthropic-ai/claude-agent-sdk";
import {
applyAskElicitationResponse,
askUserQuestionsToCreateRequest,
AskUserQuestion,
createElicitationResponseToElicitResult,
extractAskUserQuestions,
mcpElicitationToCreateRequest,
} from "../elicitation.js";
const SESSION_ID = "session-1";
/**
* Build a question matching the SDK's (strict) AskUserQuestion schema while
* keeping test sites terse. Option descriptions default to "" (the tool always
* provides one); pass a description to exercise the "label — description" title.
*/
function mkQuestion(
question: string,
options: Array<{ label: string; description?: string }>,
opts: { header?: string; multiSelect?: boolean } = {},
): AskUserQuestion {
return {
question,
header: opts.header ?? "",
multiSelect: opts.multiSelect ?? false,
options: options.map((o) => ({ label: o.label, description: o.description ?? "" })),
} as AskUserQuestion;
}
describe("mcpElicitationToCreateRequest", () => {
it("maps a form-mode request, defaulting the schema type to object", () => {
const request: ElicitationRequest = {
serverName: "server",
message: "Need your name",
mode: "form",
requestedSchema: { properties: { name: { type: "string" } } },
};
const result = mcpElicitationToCreateRequest(request, SESSION_ID);
expect(result).toEqual({
mode: "form",
sessionId: SESSION_ID,
message: "Need your name",
requestedSchema: { type: "object", properties: { name: { type: "string" } } },
});
});
it("maps a url-mode request and preserves the elicitation id", () => {
const request: ElicitationRequest = {
serverName: "server",
message: "Authorize",
mode: "url",
url: "https://example.com/auth",
elicitationId: "el-123",
};
expect(mcpElicitationToCreateRequest(request, SESSION_ID)).toEqual({
mode: "url",
sessionId: SESSION_ID,
message: "Authorize",
url: "https://example.com/auth",
elicitationId: "el-123",
});
});
it("generates an elicitation id when a url request omits one", () => {
const request: ElicitationRequest = {
serverName: "server",
message: "Authorize",
mode: "url",
url: "https://example.com/auth",
};
const result = mcpElicitationToCreateRequest(request, SESSION_ID);
expect(result?.mode).toBe("url");
expect((result as { elicitationId: string }).elicitationId).toMatch(/.+/);
});
it("returns null for a url request with no url", () => {
const request: ElicitationRequest = {
serverName: "server",
message: "Authorize",
mode: "url",
};
expect(mcpElicitationToCreateRequest(request, SESSION_ID)).toBeNull();
});
});
describe("createElicitationResponseToElicitResult", () => {
it("maps accept with content", () => {
const response = { action: "accept", content: { name: "Ada" } } as CreateElicitationResponse;
expect(createElicitationResponseToElicitResult(response)).toEqual({
action: "accept",
content: { name: "Ada" },
});
});
it("defaults accept content to an empty object", () => {
const response = { action: "accept" } as CreateElicitationResponse;
expect(createElicitationResponseToElicitResult(response)).toEqual({
action: "accept",
content: {},
});
});
it("maps decline and cancel", () => {
expect(
createElicitationResponseToElicitResult({ action: "decline" } as CreateElicitationResponse),
).toEqual({ action: "decline" });
expect(
createElicitationResponseToElicitResult({ action: "cancel" } as CreateElicitationResponse),
).toEqual({ action: "cancel" });
});
});
describe("extractAskUserQuestions", () => {
const question = mkQuestion("Which?", [{ label: "A" }, { label: "B" }]);
it("reads questions off the tool input", () => {
expect(extractAskUserQuestions({ questions: [question] })).toEqual([question]);
});
it("returns null when there are no questions", () => {
expect(extractAskUserQuestions({})).toBeNull();
expect(extractAskUserQuestions({ questions: [] })).toBeNull();
});
it("filters out malformed questions (missing options, empty options, bad question)", () => {
const input = {
questions: [
question,
{ question: "missing options" },
{ options: [] },
{ options: [], question: "empty" },
],
} as unknown as Record<string, unknown>;
expect(extractAskUserQuestions(input)).toEqual([question]);
});
it("returns null when every question is filtered out", () => {
const input = {
questions: [{ question: "no options" }, { options: [{ label: "A" }] }],
} as unknown as Record<string, unknown>;
expect(extractAskUserQuestions(input)).toBeNull();
});
});
describe("askUserQuestionsToCreateRequest", () => {
it("keys a single-select question by a stable id and carries the prompt in message only", () => {
const questions = [
mkQuestion(
"Which library?",
[{ label: "date-fns", description: "Lightweight" }, { label: "luxon" }],
{
header: "Library",
},
),
];
const result = askUserQuestionsToCreateRequest(questions, SESSION_ID, "tool-1");
expect(result).toMatchObject({
mode: "form",
sessionId: SESSION_ID,
toolCallId: "tool-1",
message: "Which library?",
});
const schema = (result as Extract<CreateElicitationRequest, { mode: "form" }>).requestedSchema;
// Nothing is required; the user can pick, type a custom answer, or skip.
expect(schema.required).toBeUndefined();
// Single question → no duplicated description (prompt is in `message`).
expect(schema.properties?.["question_0"]).toEqual({
type: "string",
title: "Library",
description: undefined,
oneOf: [
{ const: "date-fns", title: "date-fns — Lightweight" },
{ const: "luxon", title: "luxon" },
],
});
// The full question text is not used as a property key.
expect(schema.properties?.["Which library?"]).toBeUndefined();
});
it("includes an optional free-text custom-answer field", () => {
const questions = [mkQuestion("Which?", [{ label: "A" }, { label: "B" }])];
const schema = (
askUserQuestionsToCreateRequest(questions, SESSION_ID, undefined) as Extract<
CreateElicitationRequest,
{ mode: "form" }
>
).requestedSchema;
expect(schema.properties?.["customAnswer"]).toMatchObject({ type: "string", title: "Other" });
});
it("builds an array property for multi-select questions and includes per-field question text", () => {
const questions = [
mkQuestion("Pick one?", [{ label: "A" }, { label: "B" }]),
mkQuestion("Which features?", [{ label: "auth" }, { label: "logging" }], {
multiSelect: true,
}),
];
const result = askUserQuestionsToCreateRequest(questions, SESSION_ID, undefined);
const schema = (result as Extract<CreateElicitationRequest, { mode: "form" }>).requestedSchema;
expect(result).not.toHaveProperty("toolCallId");
// Multiple questions → generic message, each field carries its own question.
expect(result.message).toBe("Please answer the following questions.");
expect(schema.properties?.["question_1"]).toEqual({
type: "array",
title: undefined,
description: "Which features?",
items: {
anyOf: [
{ const: "auth", title: "auth" },
{ const: "logging", title: "logging" },
],
},
});
});
});
describe("applyAskElicitationResponse", () => {
const questions = [
mkQuestion("Single?", [{ label: "A" }, { label: "B" }]),
mkQuestion("Multi?", [{ label: "X" }, { label: "Y" }], { multiSelect: true }),
];
const toolInput = { questions, metadata: { source: "test" } };
it("writes selected labels (keyed by indexed fields) into updatedInput on accept", () => {
const response = {
action: "accept",
content: { question_0: "A", question_1: ["X", "Y"] },
} as CreateElicitationResponse;
expect(applyAskElicitationResponse(response, toolInput, questions)).toEqual({
action: "answered",
updatedInput: {
questions,
metadata: { source: "test" },
answers: { "Single?": "A", "Multi?": "X, Y" },
},
});
});
it("maps the free-text custom-answer field to the tool's response", () => {
const response = {
action: "accept",
content: { question_0: "A", customAnswer: "something else entirely" },
} as CreateElicitationResponse;
expect(applyAskElicitationResponse(response, toolInput, questions)).toEqual({
action: "answered",
updatedInput: {
questions,
metadata: { source: "test" },
answers: { "Single?": "A" },
response: "something else entirely",
},
});
});
it("ignores empty selections and blank custom answers", () => {
const response = {
action: "accept",
content: { question_1: [], customAnswer: " " },
} as CreateElicitationResponse;
expect(applyAskElicitationResponse(response, toolInput, questions)).toEqual({
action: "answered",
updatedInput: { questions, metadata: { source: "test" }, answers: {} },
});
});
it("treats decline as answered with no answers", () => {
const response = { action: "decline" } as CreateElicitationResponse;
expect(applyAskElicitationResponse(response, toolInput, questions)).toEqual({
action: "answered",
updatedInput: { questions, metadata: { source: "test" }, answers: {} },
});
});
it("returns cancel on cancel", () => {
const response = { action: "cancel" } as CreateElicitationResponse;
expect(applyAskElicitationResponse(response, toolInput, questions)).toEqual({
action: "cancel",
});
});
it("omits answers for questions the user left unanswered", () => {
const response = {
action: "accept",
content: { question_0: "B" },
} as CreateElicitationResponse;
const result = applyAskElicitationResponse(response, toolInput, questions);
expect(result.action).toBe("answered");
expect((result as Extract<typeof result, { action: "answered" }>).updatedInput.answers).toEqual(
{ "Single?": "B" },
);
});
});

View File

@@ -148,13 +148,13 @@ describe("session config options", () => {
describe("newSession returns configOptions", () => {
it("includes configOptions in the response", async () => {
const response = await agent.newSession({ cwd: "/test", mcpServers: [] });
const response = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(response.configOptions).toBeDefined();
expect(response.configOptions).toEqual(MOCK_CONFIG_OPTIONS);
});
it("includes mode and model config options", async () => {
const response = await agent.newSession({ cwd: "/test", mcpServers: [] });
const response = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
const modeOption = response.configOptions?.find((o) => o.id === "mode");
const modelOption = response.configOptions?.find((o) => o.id === "model");
expect(modeOption).toBeDefined();
@@ -173,7 +173,7 @@ describe("session config options", () => {
(agent as unknown as { loadSession: typeof loadSessionSpy }).loadSession = loadSessionSpy;
const response = await agent.loadSession({
cwd: "/test",
cwd: process.cwd(),
sessionId: SESSION_ID,
mcpServers: [],
});

View File

@@ -1,4 +1,4 @@
import { describe, it, expect } from "vitest";
import { describe, it, expect, beforeEach } from "vitest";
import { AgentSideConnection, ClientCapabilities } from "@agentclientprotocol/sdk";
import { ImageBlockParam, ToolResultBlockParam } from "@anthropic-ai/sdk/resources";
import {
@@ -673,14 +673,19 @@ describe("Bash terminal output", () => {
});
describe("toAcpNotifications with clientCapabilities", () => {
const toolUseCache: ToolUseCache = {
toolu_bash: {
type: "tool_use",
id: "toolu_bash",
name: "Bash",
input: { command: "ls -la" },
},
};
// Reset before each test: toAcpNotifications prunes the cache entry once it
// maps the tool_result, so a shared object would be empty by the 2nd test.
let toolUseCache: ToolUseCache;
beforeEach(() => {
toolUseCache = {
toolu_bash: {
type: "tool_use",
id: "toolu_bash",
name: "Bash",
input: { command: "ls -la" },
},
};
});
const bashResult: BetaBashCodeExecutionResultBlock = {
type: "bash_code_execution_result",
@@ -792,11 +797,21 @@ describe("Bash terminal output", () => {
{ clientCapabilities: { _meta: { terminal_output: true } } },
);
// Fresh cache: the withSupport call above already consumed the entry,
// since toAcpNotifications prunes the tool_use once it maps the result.
const toolUseCacheWithoutSupport: ToolUseCache = {
toolu_bash: {
type: "tool_use",
id: "toolu_bash",
name: "Bash",
input: { command: "ls -la" },
},
};
const withoutSupport = toAcpNotifications(
[toolResult],
"assistant",
"test-session",
toolUseCache,
toolUseCacheWithoutSupport,
mockClient,
mockLogger,
);
@@ -848,6 +863,31 @@ describe("Bash terminal output", () => {
});
});
describe("toolUseCache pruning", () => {
it("retains the tool_use entry until its result, then prunes it", () => {
const toolUseCache: ToolUseCache = {};
const toolUse = {
type: "tool_use" as const,
id: "toolu_read",
name: "Read",
input: { file_path: "/tmp/x.txt" },
};
// tool_use is cached and kept (the matching result hasn't arrived yet).
toAcpNotifications([toolUse], "assistant", "s", toolUseCache, mockClient, mockLogger);
expect(toolUseCache.toolu_read).toBeDefined();
// tool_result resolves it, so the entry is pruned to bound memory.
const toolResult: ToolResultBlockParam = {
type: "tool_result",
tool_use_id: "toolu_read",
content: [{ type: "text", text: "hello" }],
};
toAcpNotifications([toolResult], "assistant", "s", toolUseCache, mockClient, mockLogger);
expect(toolUseCache.toolu_read).toBeUndefined();
});
});
describe("post-tool-use hook sends diff content for Edit tool", () => {
it("should include content and locations from structuredPatch in hook update", async () => {
const toolUseCache: ToolUseCache = {};

View File

@@ -8,6 +8,7 @@ import {
import { HookCallback } from "@anthropic-ai/claude-agent-sdk";
import {
AgentInput,
AskUserQuestionInput,
BashInput,
FileEditInput,
FileReadInput,
@@ -415,6 +416,24 @@ export function toolInfoFromToolUse(
};
}
case "AskUserQuestion": {
const input = toolUse.input as Partial<AskUserQuestionInput> | undefined;
const questions = Array.isArray(input?.questions) ? input.questions : [];
return {
title:
questions.length === 1 && questions[0]?.question
? questions[0].question
: "Asking for your input",
kind: "other",
content: questions
.filter((q) => typeof q?.question === "string")
.map((q) => ({
type: "content" as const,
content: { type: "text" as const, text: q.question },
})),
};
}
case "Other": {
const input = toolUse.input;
let output;