Compare commits

...

17 Commits

Author SHA1 Message Date
Ben Brandt
325c513f1b Prep 0.14.0 release (#278) 2026-02-02 15:16:39 +00:00
Tommy D. Rossi
87fa0a23c0 feat: implement unstable_listSessions for session/list RFD (#254)
Adds support for the draft session/list RFD spec by reading Claude
Code's local session files.

- Implement `unstable_listSessions` method that parses JSONL session
files from `~/.claude/projects/`
- Advertise `list` capability in `sessionCapabilities`
- Support filtering by `cwd` parameter
- Implement cursor-based pagination (50 sessions per page)
- Extract title from first user message, use file mtime as `updatedAt`

Related: https://agentclientprotocol.com/rfds/session-list

What do you think @benbrandt? Is it ok to do the json files parsing
ourselves? I also opened [an
issue](https://github.com/anthropics/claude-agent-sdk-typescript/issues/147)
in the claude agent sdk repo but I doubt they will add that
functionality in the sdk npm package

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2026-02-02 15:14:08 +00:00
Ben Brandt
c5a49e7481 Notify when we enter plan mode (#277)
Closes #274
2026-02-02 14:37:02 +00:00
Ben Brandt
c597cfae85 Support IS_SANDBOX env var (#276)
Closes #271
2026-02-02 14:20:06 +00:00
José Valim
a31f4534dd Update CLAUDE_CONFIG_DIR environment variable usage (#269)
According to the docs, that's the new of the env var:
https://code.claude.com/docs/en/settings#environment-variables
2026-02-02 14:13:28 +00:00
Ben Brandt
2e9bb8e82b Update to claude-agent-sdk 0.2.29 (#275) 2026-02-02 14:12:21 +00:00
Pointless
cfdb6463d6 Fix glob matching for file permissions on Windows (#249)
8 tests in settings.test.ts fail on Windows because `minimatch` expects
forward slashes but `path.normalize()` returns backslashes.

Converts paths to forward slashes before matching.
2026-02-02 09:57:41 +01:00
Joshua Carver
adb495d5d3 fix: Map Claude Code tool results to valid ACP content blocks (#270)
This PR fixes an issue with `claude-code-acp` where incoming tool
results from the Anthropic SDK would, in several cases, get mapped to
invalid ACP `ContentBlock`s inside of the `toAcpContentUpdate` method.
This prevents ACP clients from being able to properly render tool calls.

Per the [ACP
spec](https://agentclientprotocol.com/protocol/schema#contentblock)
`ContentBlock` can only have a `type` field with a value of: `text |
image | audio | resource | resource_link`. However, several of Claude
Code's tools return results with different `type`'s .

This PR adds a `toAcpContentBlock` method that maps tool call results to
valid ACP `ContentBlock`. It attempts to be defensive and will map
unknown types to a text content block. This avoids breakages if
Anthropic changes the types upstream, or introduces new tools.
2026-01-29 15:51:34 +00:00
Steffen Deusch
11ca660dec allow using /context command (#268)
I experimented with https://agentclientprotocol.com/rfds/session-usage
(see
https://github.com/agentclientprotocol/agent-client-protocol/pull/415),
but the Claude Agent SDK does not seem to return the information we need
for an accurate context indicator. For example, one request to explore a
project returned 500k+ cached read tokens in Claude's `result` message,
while the agent reported 64k tokens used. I can open a PR with those
changes for reference once the Typescript SDK is updated.

To allow users to still see the current context usage, allowing to run
`/context` seems to be the second best option. This PR changes the code
to handle the `<local-command-stdout>` message that the SDK generates
when sending `/context` and includes `/context` as allowed command.

Screenshot from Tidewave with this change:

<img width="460" height="1079" alt="image"
src="https://github.com/user-attachments/assets/a3796e46-4014-4988-a993-e13d38d38d96"
/>
2026-01-29 16:34:39 +01:00
Charles Covey-Brandt
32898580a5 feat: add rawOutput to tool call updates (#261)
## Summary
- Pass the raw tool result content as `rawOutput` in `tool_call_update`
notifications
- Allows ACP clients to access the unprocessed tool output alongside the
formatted content
- Adds tests using SDK types to document exact shapes for different tool
result types

## Test plan
- [x] `npm run build` passes
- [x] `npm test` passes (86 tests)
- [x] `npm run lint` passes
- [x] `npm run format:check` passes

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-28 09:47:36 +01:00
Ben Brandt
a7794b5ffb Prep 0.13.2 release (#266) 2026-01-28 08:45:01 +00:00
Ben Brandt
8802888e39 Renable _meta.terminal-auth support (#265) 2026-01-28 08:42:21 +00:00
Ben Brandt
e4cebeca96 Update to claude-agent-sdk 0.2.22 (#264) 2026-01-28 08:41:29 +00:00
dependabot[bot]
45a539257b Bump hono from 4.11.5 to 4.11.7 in the npm_and_yarn group across 1 directory (#263)
Bumps the npm_and_yarn group with 1 update in the / directory:
[hono](https://github.com/honojs/hono).

Updates `hono` from 4.11.5 to 4.11.7
<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.11.7</h2>
<h1>Security Release</h1>
<p>This release includes security fixes for multiple vulnerabilities in
Hono and related middleware. We recommend upgrading if you are using any
of the affected components.</p>
<h2>Components</h2>
<h3>IP Restriction Middleware</h3>
<p>Fixed an IPv4 address validation bypass that could allow IP-based
access control to be bypassed under certain configurations.</p>
<h3>Cache Middleware</h3>
<p>Fixed an issue where responses marked with <code>Cache-Control:
private</code> or <code>no-store</code> could be cached, potentially
leading to information disclosure on some runtimes.</p>
<h3>Serve Static Middleware (Cloudflare Workers adapter)</h3>
<p>Fixed an issue that could allow unintended access to internal asset
keys when serving static files with user-controlled paths.</p>
<h3>hono/jsx <code>ErrorBoundary</code></h3>
<p>Fixed a reflected Cross-Site Scripting (XSS) issue in the
<code>ErrorBoundary</code> component that could occur when untrusted
strings were rendered without proper escaping.</p>
<h2>Recommendation</h2>
<p>Users are encouraged to upgrade to this release, especially if
they:</p>
<ul>
<li>Use IP Restriction Middleware</li>
<li>Use Cache Middleware on Deno, Bun, or Node.js</li>
<li>Use Serve Static Middleware with user-controlled paths on Cloudflare
Workers</li>
<li>Render untrusted data inside <code>ErrorBoundary</code>
components</li>
</ul>
<h2>Security Advisories &amp; CVEs</h2>
<ul>
<li>
<p><strong>IP Restriction Middleware – IPv4 address validation
bypass</strong></p>
<ul>
<li>Advisory: <a
href="https://github.com/honojs/hono/security/advisories/GHSA-r354-f388-2fhh">https://github.com/honojs/hono/security/advisories/GHSA-r354-f388-2fhh</a></li>
<li>CVE: CVE-2026-24398</li>
</ul>
</li>
<li>
<p><strong>Cache Middleware ignores <code>Cache-Control:
private</code></strong></p>
<ul>
<li>Advisory: <a
href="https://github.com/honojs/hono/security/advisories/GHSA-6wqw-2p9w-4vw4">https://github.com/honojs/hono/security/advisories/GHSA-6wqw-2p9w-4vw4</a></li>
<li>CVE: CVE-2026-24472</li>
</ul>
</li>
<li>
<p><strong>Serve Static Middleware (Cloudflare Workers adapter) –
Arbitrary key read</strong></p>
<ul>
<li>Advisory: <a
href="https://github.com/honojs/hono/security/advisories/GHSA-w332-q679-j88p">https://github.com/honojs/hono/security/advisories/GHSA-w332-q679-j88p</a></li>
<li>CVE: CVE-2026-24473</li>
</ul>
</li>
<li>
<p><strong>hono/jsx <code>ErrorBoundary</code> – Cross-Site Scripting
(XSS)</strong></p>
<ul>
<li>Advisory: <a
href="https://github.com/honojs/hono/security/advisories/GHSA-9r54-q6cx-xmh5">https://github.com/honojs/hono/security/advisories/GHSA-9r54-q6cx-xmh5</a></li>
<li>CVE: Pending</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f7d272abe1"><code>f7d272a</code></a>
4.11.7</li>
<li><a
href="2cf60046d7"><code>2cf6004</code></a>
Merge commit from fork</li>
<li><a
href="cf9a78db4d"><code>cf9a78d</code></a>
Merge commit from fork</li>
<li><a
href="edbf6eea8e"><code>edbf6ee</code></a>
Merge commit from fork</li>
<li><a
href="12c511745b"><code>12c5117</code></a>
Merge commit from fork</li>
<li><a
href="7343487e62"><code>7343487</code></a>
4.11.6</li>
<li><a
href="b6e5a97cd4"><code>b6e5a97</code></a>
feat(bun): export getBunServer (<a
href="https://redirect.github.com/honojs/hono/issues/4626">#4626</a>)</li>
<li><a
href="2a9cd953b6"><code>2a9cd95</code></a>
fix(sse): handle <code>\r</code> and <code>\r\n</code> line endings in
writeSSE (<a
href="https://redirect.github.com/honojs/hono/issues/4644">#4644</a>)</li>
<li><a
href="8078bbf759"><code>8078bbf</code></a>
docs: align CODE_OF_CONDUCT.md wording with Contributor Covenant (<a
href="https://redirect.github.com/honojs/hono/issues/4630">#4630</a>)</li>
<li><a
href="a5be555b31"><code>a5be555</code></a>
refactor: use <code>unique symbol</code> for more accurate typing. (<a
href="https://redirect.github.com/honojs/hono/issues/4651">#4651</a>)</li>
<li>See full diff in <a
href="https://github.com/honojs/hono/compare/v4.11.5...v4.11.7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=hono&package-manager=npm_and_yarn&previous-version=4.11.5&new-version=4.11.7)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/zed-industries/claude-code-acp/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-27 22:53:21 +01:00
Ben Brandt
670fb18728 Update to claude-agent-sdk 0.2.19 (#260) 2026-01-26 10:15:54 +00:00
codeignus
36ea238d76 fix: return content from Write tool (#250)
## Problem
The Write tool was returning an empty content array, which causes errors
with some AI providers when using Claude Code through ACP.

Specifically, Z.AI GLM-4.7 returns error 1213 "The prompt parameter was
not received normally" when the Write tool returns empty content.
This error causes the API request to fail and halts the conversation
flow, even though the file write itself succeeds.

Note: The Claude Code CLI works fine - this issue is specific to the ACP
integration.

## Solution
Return a confirmation message from the Write tool, aligning it with
Read, Edit, and Bash tools which all return content.

## Changes
- Write tool now returns `content: [{type: "text", text: "Wrote file:
${input.file_path}"}]`

This change only adds a confirmation message - no existing functionality
is modified.

## Testing
Tested with Zed editor. Would appreciate others verifying it works as
expected.

Relates to zed-industries/zed#45704

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2026-01-19 15:27:32 +01:00
Charles Covey-Brandt
8366864daf Exclude tests from published package (#248)
## Summary
- Add `!dist/tests/` to package.json files array to prevent test files
from being included in npm package
- Add `.claude/*.local.*` to .gitignore for local config files

## Test plan
- [x] Verified with `npm pack --dry-run` that dist/tests/ is excluded
from package

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-16 17:33:43 +01:00
12 changed files with 1646 additions and 310 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
node_modules
dist
.DS_Store
.claude/*.local.*
CLAUDE.local.md

View File

@@ -1,5 +1,21 @@
# Changelog
## 0.14.0
- Update to @anthropic-ai/claude-agent-sdk@0.2.29
- Update to using the recommended `CLAUDE_CONFIG_DIR` env variable for setting where config files are kept
- Support /context command
- Fix incorrect context type mapping for tool calls
- Fix glob metching for file permissions on Windows
- Support the `IS_SANDBOX` env var for supporting bypass permissions in root mode
- Fix missing notification for entering plan mode
- Experimental unstable support for listing sessions
## 0.13.2
- Update to @anthropic-ai/claude-agent-sdk@0.2.22
- Fix: return content from ACP write tool to help with issues with alternate providers.
## 0.13.1
- Update to @anthropic-ai/claude-agent-sdk@0.2.7

492
package-lock.json generated
View File

@@ -1,17 +1,17 @@
{
"name": "@zed-industries/claude-code-acp",
"version": "0.13.1",
"version": "0.14.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@zed-industries/claude-code-acp",
"version": "0.13.1",
"version": "0.14.0",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "0.13.0",
"@anthropic-ai/claude-agent-sdk": "0.2.7",
"@modelcontextprotocol/sdk": "1.25.2",
"@agentclientprotocol/sdk": "0.13.1",
"@anthropic-ai/claude-agent-sdk": "0.2.29",
"@modelcontextprotocol/sdk": "1.25.3",
"diff": "8.0.3",
"minimatch": "10.1.1"
},
@@ -19,32 +19,32 @@
"claude-code-acp": "dist/index.js"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.71.2",
"@types/node": "25.0.8",
"@typescript-eslint/eslint-plugin": "8.53.0",
"@typescript-eslint/parser": "8.53.0",
"@anthropic-ai/sdk": "0.72.1",
"@types/node": "25.2.0",
"@typescript-eslint/eslint-plugin": "8.54.0",
"@typescript-eslint/parser": "8.54.0",
"eslint": "9.39.2",
"eslint-config-prettier": "10.1.8",
"globals": "17.0.0",
"prettier": "3.8.0",
"globals": "17.3.0",
"prettier": "3.8.1",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vitest": "4.0.17"
"vitest": "4.0.18"
}
},
"node_modules/@agentclientprotocol/sdk": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.13.0.tgz",
"integrity": "sha512-Z6/Fp4cXLbYdMXr5AK752JM5qG2VKb6ShM0Ql6FimBSckMmLyK54OA20UhPYoH4C37FSFwUTARuwQOwQUToYrw==",
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.13.1.tgz",
"integrity": "sha512-6byvu+F/xc96GBkdAx4hq6/tB3vT63DSBO4i3gYCz8nuyZMerVFna2Gkhm8EHNpZX0J9DjUxzZCW+rnHXUg0FA==",
"license": "Apache-2.0",
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"node_modules/@anthropic-ai/claude-agent-sdk": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.7.tgz",
"integrity": "sha512-I1/zcnLah74kZeRkj/1QnDaC6ItJ2m/Bftlm25uoaRkZx7i7SkcpqM9jGE/r2A8PMxnw5WpabP60Xgj99CrTuw==",
"version": "0.2.29",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.29.tgz",
"integrity": "sha512-b+655n4ZqqAiMQEL3P44e9UurkI7WWanWTQQQTEcKngL5YCjjXExEPEJRxrmqp8mQXs0kLErZhObx0ZuwibOhA==",
"license": "SEE LICENSE IN README.md",
"engines": {
"node": ">=18.0.0"
@@ -64,9 +64,9 @@
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.71.2",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.71.2.tgz",
"integrity": "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ==",
"version": "0.72.1",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.72.1.tgz",
"integrity": "sha512-MiUnue7qN7DvLIoYHgkedN2z05mRf2CutBzjXXY2krzOhG2r/rIfISS2uVkNLikgToB5hYIzw+xp2jdOtRkqYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1152,12 +1152,12 @@
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.25.2",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz",
"integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==",
"version": "1.25.3",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.3.tgz",
"integrity": "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==",
"license": "MIT",
"dependencies": {
"@hono/node-server": "^1.19.7",
"@hono/node-server": "^1.19.9",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"content-type": "^1.0.5",
@@ -1191,9 +1191,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
"integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
"integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
"cpu": [
"arm"
],
@@ -1205,9 +1205,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz",
"integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
"integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
"cpu": [
"arm64"
],
@@ -1219,9 +1219,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
"integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
"integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
"cpu": [
"arm64"
],
@@ -1233,9 +1233,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz",
"integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
"integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
"cpu": [
"x64"
],
@@ -1247,9 +1247,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz",
"integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
"integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
"cpu": [
"arm64"
],
@@ -1261,9 +1261,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz",
"integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
"integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
"cpu": [
"x64"
],
@@ -1275,9 +1275,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz",
"integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
"integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
"cpu": [
"arm"
],
@@ -1289,9 +1289,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz",
"integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
"integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
"cpu": [
"arm"
],
@@ -1303,9 +1303,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz",
"integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
"integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
"cpu": [
"arm64"
],
@@ -1317,9 +1317,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz",
"integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
"integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
"cpu": [
"arm64"
],
@@ -1331,9 +1331,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz",
"integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
"integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
"cpu": [
"loong64"
],
@@ -1345,9 +1345,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz",
"integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
"integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
"cpu": [
"loong64"
],
@@ -1359,9 +1359,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz",
"integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
"integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
"cpu": [
"ppc64"
],
@@ -1373,9 +1373,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz",
"integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
"integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
"cpu": [
"ppc64"
],
@@ -1387,9 +1387,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz",
"integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
"integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
"cpu": [
"riscv64"
],
@@ -1401,9 +1401,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz",
"integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
"integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
"cpu": [
"riscv64"
],
@@ -1415,9 +1415,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz",
"integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
"integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
"cpu": [
"s390x"
],
@@ -1429,9 +1429,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz",
"integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
"integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
"cpu": [
"x64"
],
@@ -1443,9 +1443,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz",
"integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
"integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
"cpu": [
"x64"
],
@@ -1457,9 +1457,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz",
"integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
"integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
"cpu": [
"x64"
],
@@ -1471,9 +1471,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz",
"integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
"integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
"cpu": [
"arm64"
],
@@ -1485,9 +1485,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz",
"integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
"integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
"cpu": [
"arm64"
],
@@ -1499,9 +1499,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz",
"integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
"integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
"cpu": [
"ia32"
],
@@ -1513,9 +1513,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz",
"integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
"integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
"cpu": [
"x64"
],
@@ -1527,9 +1527,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz",
"integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
"integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
"cpu": [
"x64"
],
@@ -1608,9 +1608,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.0.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz",
"integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==",
"version": "25.2.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz",
"integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1618,17 +1618,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz",
"integrity": "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.53.0",
"@typescript-eslint/type-utils": "8.53.0",
"@typescript-eslint/utils": "8.53.0",
"@typescript-eslint/visitor-keys": "8.53.0",
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/type-utils": "8.54.0",
"@typescript-eslint/utils": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.4.0"
@@ -1641,22 +1641,22 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.53.0",
"@typescript-eslint/parser": "^8.54.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.0.tgz",
"integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.53.0",
"@typescript-eslint/types": "8.53.0",
"@typescript-eslint/typescript-estree": "8.53.0",
"@typescript-eslint/visitor-keys": "8.53.0",
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/typescript-estree": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0",
"debug": "^4.4.3"
},
"engines": {
@@ -1672,14 +1672,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.0.tgz",
"integrity": "sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.53.0",
"@typescript-eslint/types": "^8.53.0",
"@typescript-eslint/tsconfig-utils": "^8.54.0",
"@typescript-eslint/types": "^8.54.0",
"debug": "^4.4.3"
},
"engines": {
@@ -1694,14 +1694,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz",
"integrity": "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.53.0",
"@typescript-eslint/visitor-keys": "8.53.0"
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1712,9 +1712,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz",
"integrity": "sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1729,15 +1729,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz",
"integrity": "sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.53.0",
"@typescript-eslint/typescript-estree": "8.53.0",
"@typescript-eslint/utils": "8.53.0",
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/typescript-estree": "8.54.0",
"@typescript-eslint/utils": "8.54.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.4.0"
},
@@ -1754,9 +1754,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz",
"integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1768,16 +1768,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz",
"integrity": "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.53.0",
"@typescript-eslint/tsconfig-utils": "8.53.0",
"@typescript-eslint/types": "8.53.0",
"@typescript-eslint/visitor-keys": "8.53.0",
"@typescript-eslint/project-service": "8.54.0",
"@typescript-eslint/tsconfig-utils": "8.54.0",
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/visitor-keys": "8.54.0",
"debug": "^4.4.3",
"minimatch": "^9.0.5",
"semver": "^7.7.3",
@@ -1822,16 +1822,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.0.tgz",
"integrity": "sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.53.0",
"@typescript-eslint/types": "8.53.0",
"@typescript-eslint/typescript-estree": "8.53.0"
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
"@typescript-eslint/typescript-estree": "8.54.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1846,13 +1846,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz",
"integrity": "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==",
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.53.0",
"@typescript-eslint/types": "8.54.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -1877,16 +1877,16 @@
}
},
"node_modules/@vitest/expect": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz",
"integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
"integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@types/chai": "^5.2.2",
"@vitest/spy": "4.0.17",
"@vitest/utils": "4.0.17",
"@vitest/spy": "4.0.18",
"@vitest/utils": "4.0.18",
"chai": "^6.2.1",
"tinyrainbow": "^3.0.3"
},
@@ -1895,13 +1895,13 @@
}
},
"node_modules/@vitest/mocker": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz",
"integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
"integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "4.0.17",
"@vitest/spy": "4.0.18",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -1922,9 +1922,9 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz",
"integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
"integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1935,13 +1935,13 @@
}
},
"node_modules/@vitest/runner": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.17.tgz",
"integrity": "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
"integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.0.17",
"@vitest/utils": "4.0.18",
"pathe": "^2.0.3"
},
"funding": {
@@ -1949,13 +1949,13 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz",
"integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
"integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.0.17",
"@vitest/pretty-format": "4.0.18",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -1964,9 +1964,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz",
"integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
"integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1974,13 +1974,13 @@
}
},
"node_modules/@vitest/utils": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz",
"integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
"integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.0.17",
"@vitest/pretty-format": "4.0.18",
"tinyrainbow": "^3.0.3"
},
"funding": {
@@ -2294,9 +2294,9 @@
}
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
@@ -2304,6 +2304,10 @@
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/create-require": {
@@ -3066,9 +3070,9 @@
}
},
"node_modules/globals": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.0.0.tgz",
"integrity": "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==",
"version": "17.3.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz",
"integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3125,9 +3129,9 @@
}
},
"node_modules/hono": {
"version": "4.11.4",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz",
"integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==",
"version": "4.11.7",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz",
"integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
"license": "MIT",
"peer": true,
"engines": {
@@ -3724,9 +3728,9 @@
}
},
"node_modules/prettier": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz",
"integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==",
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -3821,9 +3825,9 @@
}
},
"node_modules/rollup": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
"integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==",
"version": "4.57.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
"integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3837,31 +3841,31 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.55.1",
"@rollup/rollup-android-arm64": "4.55.1",
"@rollup/rollup-darwin-arm64": "4.55.1",
"@rollup/rollup-darwin-x64": "4.55.1",
"@rollup/rollup-freebsd-arm64": "4.55.1",
"@rollup/rollup-freebsd-x64": "4.55.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.55.1",
"@rollup/rollup-linux-arm-musleabihf": "4.55.1",
"@rollup/rollup-linux-arm64-gnu": "4.55.1",
"@rollup/rollup-linux-arm64-musl": "4.55.1",
"@rollup/rollup-linux-loong64-gnu": "4.55.1",
"@rollup/rollup-linux-loong64-musl": "4.55.1",
"@rollup/rollup-linux-ppc64-gnu": "4.55.1",
"@rollup/rollup-linux-ppc64-musl": "4.55.1",
"@rollup/rollup-linux-riscv64-gnu": "4.55.1",
"@rollup/rollup-linux-riscv64-musl": "4.55.1",
"@rollup/rollup-linux-s390x-gnu": "4.55.1",
"@rollup/rollup-linux-x64-gnu": "4.55.1",
"@rollup/rollup-linux-x64-musl": "4.55.1",
"@rollup/rollup-openbsd-x64": "4.55.1",
"@rollup/rollup-openharmony-arm64": "4.55.1",
"@rollup/rollup-win32-arm64-msvc": "4.55.1",
"@rollup/rollup-win32-ia32-msvc": "4.55.1",
"@rollup/rollup-win32-x64-gnu": "4.55.1",
"@rollup/rollup-win32-x64-msvc": "4.55.1",
"@rollup/rollup-android-arm-eabi": "4.57.1",
"@rollup/rollup-android-arm64": "4.57.1",
"@rollup/rollup-darwin-arm64": "4.57.1",
"@rollup/rollup-darwin-x64": "4.57.1",
"@rollup/rollup-freebsd-arm64": "4.57.1",
"@rollup/rollup-freebsd-x64": "4.57.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
"@rollup/rollup-linux-arm-musleabihf": "4.57.1",
"@rollup/rollup-linux-arm64-gnu": "4.57.1",
"@rollup/rollup-linux-arm64-musl": "4.57.1",
"@rollup/rollup-linux-loong64-gnu": "4.57.1",
"@rollup/rollup-linux-loong64-musl": "4.57.1",
"@rollup/rollup-linux-ppc64-gnu": "4.57.1",
"@rollup/rollup-linux-ppc64-musl": "4.57.1",
"@rollup/rollup-linux-riscv64-gnu": "4.57.1",
"@rollup/rollup-linux-riscv64-musl": "4.57.1",
"@rollup/rollup-linux-s390x-gnu": "4.57.1",
"@rollup/rollup-linux-x64-gnu": "4.57.1",
"@rollup/rollup-linux-x64-musl": "4.57.1",
"@rollup/rollup-openbsd-x64": "4.57.1",
"@rollup/rollup-openharmony-arm64": "4.57.1",
"@rollup/rollup-win32-arm64-msvc": "4.57.1",
"@rollup/rollup-win32-ia32-msvc": "4.57.1",
"@rollup/rollup-win32-x64-gnu": "4.57.1",
"@rollup/rollup-win32-x64-msvc": "4.57.1",
"fsevents": "~2.3.2"
}
},
@@ -4228,9 +4232,9 @@
}
},
"node_modules/ts-node/node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
@@ -4396,19 +4400,19 @@
}
},
"node_modules/vitest": {
"version": "4.0.17",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.17.tgz",
"integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "4.0.17",
"@vitest/mocker": "4.0.17",
"@vitest/pretty-format": "4.0.17",
"@vitest/runner": "4.0.17",
"@vitest/snapshot": "4.0.17",
"@vitest/spy": "4.0.17",
"@vitest/utils": "4.0.17",
"@vitest/expect": "4.0.18",
"@vitest/mocker": "4.0.18",
"@vitest/pretty-format": "4.0.18",
"@vitest/runner": "4.0.18",
"@vitest/snapshot": "4.0.18",
"@vitest/spy": "4.0.18",
"@vitest/utils": "4.0.18",
"es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2",
"magic-string": "^0.30.21",
@@ -4436,10 +4440,10 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.0.17",
"@vitest/browser-preview": "4.0.17",
"@vitest/browser-webdriverio": "4.0.17",
"@vitest/ui": "4.0.17",
"@vitest/browser-playwright": "4.0.18",
"@vitest/browser-preview": "4.0.18",
"@vitest/browser-webdriverio": "4.0.18",
"@vitest/ui": "4.0.18",
"happy-dom": "*",
"jsdom": "*"
},
@@ -4545,9 +4549,9 @@
}
},
"node_modules/zod": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"

View File

@@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "0.13.1",
"version": "0.14.0",
"description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",
"main": "dist/lib.js",
"types": "dist/lib.d.ts",
@@ -20,6 +20,7 @@
},
"files": [
"dist/",
"!dist/tests/",
"README.md",
"LICENSE",
"package.json"
@@ -59,23 +60,23 @@
"author": "Zed Industries",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "0.13.0",
"@anthropic-ai/claude-agent-sdk": "0.2.7",
"@modelcontextprotocol/sdk": "1.25.2",
"@agentclientprotocol/sdk": "0.13.1",
"@anthropic-ai/claude-agent-sdk": "0.2.29",
"@modelcontextprotocol/sdk": "1.25.3",
"diff": "8.0.3",
"minimatch": "10.1.1"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.71.2",
"@types/node": "25.0.8",
"@typescript-eslint/eslint-plugin": "8.53.0",
"@typescript-eslint/parser": "8.53.0",
"@anthropic-ai/sdk": "0.72.1",
"@types/node": "25.2.0",
"@typescript-eslint/eslint-plugin": "8.54.0",
"@typescript-eslint/parser": "8.54.0",
"eslint": "9.39.2",
"eslint-config-prettier": "10.1.8",
"globals": "17.0.0",
"prettier": "3.8.0",
"globals": "17.3.0",
"prettier": "3.8.1",
"ts-node": "10.9.2",
"typescript": "5.9.3",
"vitest": "4.0.17"
"vitest": "4.0.18"
}
}

View File

@@ -9,6 +9,8 @@ import {
ForkSessionResponse,
InitializeRequest,
InitializeResponse,
ListSessionsRequest,
ListSessionsResponse,
ndJsonStream,
NewSessionRequest,
NewSessionResponse,
@@ -19,6 +21,7 @@ import {
RequestError,
ResumeSessionRequest,
ResumeSessionResponse,
SessionInfo,
SessionModelState,
SessionNotification,
SetSessionModelRequest,
@@ -60,8 +63,44 @@ import { ContentBlockParam } from "@anthropic-ai/sdk/resources";
import { BetaContentBlock, BetaRawContentBlockDelta } from "@anthropic-ai/sdk/resources/beta.mjs";
import packageJson from "../package.json" with { type: "json" };
import { randomUUID } from "node:crypto";
import { fileURLToPath } from "node:url";
export const CLAUDE_CONFIG_DIR = process.env.CLAUDE ?? path.join(os.homedir(), ".claude");
export const CLAUDE_CONFIG_DIR =
process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
/**
* Decode a Claude project path encoding back to the original filesystem path.
* Claude encodes paths by replacing path separators with dashes:
* - Unix: "/Users/morse/project" -> "-Users-morse-project"
* - Windows: "C:\Users\morse\project" -> "C-Users-morse-project"
*/
function decodeProjectPath(encodedPath: string): string {
// Check if this looks like a Windows path (starts with drive letter pattern like "C-")
const windowsDriveMatch = encodedPath.match(/^([A-Za-z])-/);
if (windowsDriveMatch) {
// Windows path: "C-Users-morse-project" -> "C:\Users\morse\project"
const driveLetter = windowsDriveMatch[1];
const rest = encodedPath.slice(2); // Skip "C-"
return `${driveLetter}:\\${rest.replace(/-/g, "\\")}`;
}
// Unix path: "-Users-morse-project" -> "/Users/morse/project"
return encodedPath.replace(/-/g, "/");
}
const MAX_TITLE_LENGTH = 128;
function sanitizeTitle(text: string): string {
// Replace newlines and collapse whitespace
const sanitized = text
.replace(/[\r\n]+/g, " ")
.replace(/\s+/g, " ")
.trim();
if (sanitized.length <= MAX_TITLE_LENGTH) {
return sanitized;
}
return sanitized.slice(0, MAX_TITLE_LENGTH - 1) + "…";
}
/**
* Logger interface for customizing logging output
@@ -124,17 +163,18 @@ export type ToolUpdateMeta = {
};
};
type ToolUseCache = {
export type ToolUseCache = {
[key: string]: {
type: "tool_use" | "server_tool_use" | "mcp_tool_use";
id: string;
name: string;
input: any;
input: unknown;
};
};
// Bypass Permissions doesn't work if we are a root/sudo user
const IS_ROOT = (process.geteuid?.() ?? process.getuid?.()) === 0;
const ALLOW_BYPASS = !IS_ROOT || !!process.env.IS_SANDBOX;
// Implement the ACP Agent interface
export class ClaudeAcpAgent implements Agent {
@@ -165,17 +205,17 @@ export class ClaudeAcpAgent implements Agent {
};
// If client supports terminal-auth capability, use that instead.
// if (request.clientCapabilities?._meta?.["terminal-auth"] === true) {
// const cliPath = fileURLToPath(import.meta.resolve("@anthropic-ai/claude-agent-sdk/cli.js"));
if (request.clientCapabilities?._meta?.["terminal-auth"] === true) {
const cliPath = fileURLToPath(import.meta.resolve("@anthropic-ai/claude-agent-sdk/cli.js"));
// authMethod._meta = {
// "terminal-auth": {
// command: "node",
// args: [cliPath, "/login"],
// label: "Claude Code Login",
// },
// };
// }
authMethod._meta = {
"terminal-auth": {
command: "node",
args: [cliPath, "/login"],
label: "Claude Code Login",
},
};
}
return {
protocolVersion: 1,
@@ -190,6 +230,7 @@ export class ClaudeAcpAgent implements Agent {
},
sessionCapabilities: {
fork: {},
list: {},
resume: {},
},
},
@@ -245,6 +286,149 @@ export class ClaudeAcpAgent implements Agent {
return response;
}
/**
* List Claude Code sessions by parsing JSONL files
* Sessions are stored in ~/.claude/projects/<path-encoded>/
* Implements the draft session/list RFD spec
*/
async unstable_listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse> {
// Note: We load all sessions into memory for sorting, so pagination here is for
// API response size limits rather than memory efficiency. This matches the RFD spec.
const PAGE_SIZE = 50;
const claudeDir = path.join(CLAUDE_CONFIG_DIR, "projects");
try {
await fs.promises.access(claudeDir);
} catch {
return { sessions: [] };
}
// Collect all sessions across all project directories
const allSessions: SessionInfo[] = [];
try {
const projectDirs = await fs.promises.readdir(claudeDir);
for (const encodedPath of projectDirs) {
const projectDir = path.join(claudeDir, encodedPath);
const stat = await fs.promises.stat(projectDir);
if (!stat.isDirectory()) continue;
// Decode the path based on platform:
// - Unix: "-Users-morse-project" -> "/Users/morse/project"
// - Windows: "C-Users-morse-project" -> "C:\Users\morse\project"
const decodedCwd = decodeProjectPath(encodedPath);
// Skip if filtering by cwd and this doesn't match
if (params.cwd && decodedCwd !== params.cwd) continue;
const files = await fs.promises.readdir(projectDir);
// Filter to user session files only. Skip agent-*.jsonl files which contain
// internal agent metadata and system logs, not user-visible conversation sessions.
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
for (const file of jsonlFiles) {
const filePath = path.join(projectDir, file);
try {
const content = await fs.promises.readFile(filePath, "utf-8");
const lines = content.trim().split("\n").filter(Boolean);
const firstLine = lines[0];
if (!firstLine) continue;
// Parse first line to get session info
const firstEntry = JSON.parse(firstLine);
const sessionId = firstEntry.sessionId || file.replace(".jsonl", "");
// Find first user message for title
let title: string | undefined;
for (const line of lines) {
try {
const entry = JSON.parse(line);
if (entry.type === "user" && entry.message?.content) {
const msgContent = entry.message.content;
if (typeof msgContent === "string") {
title = sanitizeTitle(msgContent);
break;
}
if (Array.isArray(msgContent) && msgContent.length > 0) {
const first = msgContent[0];
const text =
typeof first === "string"
? first
: first && typeof first === "object" && typeof first.text === "string"
? first.text
: undefined;
if (text) {
title = sanitizeTitle(text);
break;
}
}
}
} catch {
// Skip malformed lines
}
}
// Get file modification time as updatedAt
const fileStat = await fs.promises.stat(filePath);
const updatedAt = fileStat.mtime.toISOString();
allSessions.push({
sessionId,
cwd: decodedCwd,
title: title ?? null,
updatedAt,
});
} catch (err) {
this.logger.error(
`[unstable_listSessions] Failed to parse session file: ${filePath}`,
err,
);
}
}
}
} catch (err) {
this.logger.error("[unstable_listSessions] Failed to list sessions", err);
return { sessions: [] };
}
// Sort by updatedAt descending (most recent first)
allSessions.sort((a, b) => {
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
return timeB - timeA;
});
// Handle pagination with cursor
let startIndex = 0;
if (params.cursor) {
try {
const decoded = Buffer.from(params.cursor, "base64").toString("utf-8");
const cursorData = JSON.parse(decoded);
startIndex = cursorData.offset ?? 0;
} catch {
// Invalid cursor, start from beginning
}
}
const pageOfSessions = allSessions.slice(startIndex, startIndex + PAGE_SIZE);
const hasMore = startIndex + PAGE_SIZE < allSessions.length;
const response: ListSessionsResponse = {
sessions: pageOfSessions,
};
if (hasMore) {
const nextCursor = Buffer.from(JSON.stringify({ offset: startIndex + PAGE_SIZE })).toString(
"base64",
);
response.nextCursor = nextCursor;
}
return response;
}
async authenticate(_params: AuthenticateRequest): Promise<void> {
throw new Error("Method not implemented.");
}
@@ -274,8 +458,12 @@ export class ClaudeAcpAgent implements Agent {
case "init":
break;
case "compact_boundary":
case "hook_started":
case "task_notification":
case "hook_progress":
case "hook_response":
case "status":
case "files_persisted":
// Todo: process via status api: https://docs.claude.com/en/docs/claude-code/hooks#hook-output
break;
default:
@@ -346,6 +534,21 @@ export class ClaudeAcpAgent implements Agent {
typeof message.message.content === "string" &&
message.message.content.includes("<local-command-stdout>")
) {
// Handle /context by sending its reply as regular agent message.
if (message.message.content.includes("Context Usage")) {
for (const notification of toAcpNotifications(
message.message.content
.replace("<local-command-stdout>", "")
.replace("</local-command-stdout>", ""),
"assistant",
params.sessionId,
this.toolUseCache,
this.client,
this.logger,
)) {
await this.client.sessionUpdate(notification);
}
}
this.logger.log(message.message.content);
break;
}
@@ -398,6 +601,7 @@ export class ClaudeAcpAgent implements Agent {
break;
}
case "tool_progress":
case "tool_use_summary":
break;
case "auth_status":
break;
@@ -688,7 +892,7 @@ export class ClaudeAcpAgent implements Agent {
extraArgs,
// If we want bypassPermissions to be an option, we have to allow it here.
// But it doesn't work in root mode, so we only activate it if it will work.
allowDangerouslySkipPermissions: !IS_ROOT,
allowDangerouslySkipPermissions: ALLOW_BYPASS,
permissionMode,
canUseTool: this.canUseTool(sessionId),
// note: although not documented by the types, passing an absolute path
@@ -709,7 +913,23 @@ export class ClaudeAcpAgent implements Agent {
PostToolUse: [
...(userProvidedOptions?.hooks?.PostToolUse || []),
{
hooks: [createPostToolUseHook(this.logger)],
hooks: [
createPostToolUseHook(this.logger, {
onEnterPlanMode: async () => {
const session = this.sessions[sessionId];
if (session) {
session.permissionMode = "plan";
}
await this.client.sessionUpdate({
sessionId,
update: {
sessionUpdate: "current_mode_update",
currentModeId: "plan",
},
});
},
}),
],
},
],
},
@@ -827,7 +1047,7 @@ export class ClaudeAcpAgent implements Agent {
},
];
// Only works in non-root mode
if (!IS_ROOT) {
if (ALLOW_BYPASS) {
availableModes.push({
id: "bypassPermissions",
name: "Bypass Permissions",
@@ -867,7 +1087,6 @@ async function getAvailableModels(query: Query): Promise<SessionModelState> {
async function getAvailableSlashCommands(query: Query): Promise<AvailableCommand[]> {
const UNSUPPORTED_COMMANDS = [
"context",
"cost",
"login",
"logout",
@@ -1146,6 +1365,7 @@ export function toAcpNotifications(
toolCallId: chunk.tool_use_id,
sessionUpdate: "tool_call_update",
status: "is_error" in chunk && chunk.is_error ? "failed" : "completed",
rawOutput: chunk.content,
...toolUpdateFromToolResult(chunk, toolUseCache[chunk.tool_use_id]),
};
}

View File

@@ -4,7 +4,7 @@ import {
FileEditInput,
FileReadInput,
FileWriteInput,
KillShellInput,
TaskOutputInput,
} from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
import { z } from "zod";
import { CLAUDE_CONFIG_DIR, ClaudeAcpAgent } from "./acp-agent.js";
@@ -269,7 +269,12 @@ Usage:
await writeTextFile(input);
return {
content: [],
content: [
{
type: "text",
text: `The file ${input.file_path} has been updated successfully.`,
},
],
};
} catch (error) {
return {
@@ -552,19 +557,66 @@ Output: Create directory 'foo'`),
In sessions with ${acpToolNames.bashOutput} always use it for output from Bash commands instead of TaskOutput.`,
inputSchema: {
bash_id: z
task_id: z
.string()
.describe(
`The id of the background bash command as returned by \`${acpToolNames.bash}\``,
),
block: z.boolean().describe("Whether to wait for completion"),
timeout: z.number().describe("Max wait time in ms"),
},
},
async (input) => {
async (input: TaskOutputInput) => {
try {
const bgTerm = agent.backgroundTerminals[input.bash_id];
const bgTerm = agent.backgroundTerminals[input.task_id];
if (!bgTerm) {
throw new Error(`Unknown shell ${input.bash_id}`);
throw new Error(`Unknown shell ${input.task_id}`);
}
if (input.block && bgTerm.status === "started") {
const statusPromise = Promise.race([
bgTerm.handle
.waitForExit()
.then((exitStatus) => ({ status: "exited" as const, exitStatus })),
sleep(input.timeout ?? 2 * 60 * 1000).then(async () => {
if (bgTerm.status === "started") {
await bgTerm.handle.kill();
}
return { status: "timedOut" as const, exitStatus: null };
}),
]);
const { status, exitStatus } = await statusPromise;
const currentOutput = await bgTerm.handle.currentOutput();
const strippedOutput = stripCommonPrefix(
bgTerm.lastOutput?.output ?? "",
currentOutput.output,
);
agent.backgroundTerminals[input.task_id] = {
status,
pendingOutput: {
...currentOutput,
output: strippedOutput,
exitStatus: exitStatus ?? currentOutput.exitStatus,
},
};
await bgTerm.handle.release();
return {
content: [
{
type: "text",
text: toolCommandOutput(status, {
...currentOutput,
output: strippedOutput,
exitStatus: exitStatus ?? currentOutput.exitStatus,
}),
},
],
};
}
if (bgTerm.status === "started") {
@@ -628,7 +680,7 @@ In sessions with ${acpToolNames.killShell} always use it instead of KillShell.`,
),
},
},
async (input: KillShellInput) => {
async (input) => {
try {
const bgTerm = agent.backgroundTerminals[input.shell_id];

View File

@@ -128,7 +128,8 @@ function normalizePath(filePath: string, cwd: string): string {
} else if (!path.isAbsolute(filePath)) {
filePath = path.join(cwd, filePath);
}
return path.normalize(filePath);
// Convert backslashes to forward slashes for minimatch compatibility on Windows
return path.normalize(filePath).replace(/\\/g, "/");
}
/**

View File

@@ -1,6 +1,6 @@
---
description: Say hello
argument-hint: [name]
argument-hint: name
---
Respond with "Hello $1" and nothing else.

View File

@@ -21,6 +21,13 @@ import { markdownEscape, toolInfoFromToolUse, toolUpdateFromToolResult } from ".
import { toAcpNotifications, promptToClaude } from "../acp-agent.js";
import { query, SDKAssistantMessage } from "@anthropic-ai/claude-agent-sdk";
import { randomUUID } from "crypto";
import type {
BetaToolResultBlockParam,
BetaToolSearchToolResultBlockParam,
BetaWebSearchToolResultBlockParam,
BetaWebFetchToolResultBlockParam,
BetaCodeExecutionToolResultBlockParam,
} from "@anthropic-ai/sdk/resources/beta.mjs";
describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration", () => {
let child: ReturnType<typeof spawn>;
@@ -734,6 +741,200 @@ describe("tool conversions", () => {
],
});
});
it("should transform tool_reference content to valid ACP content", () => {
const toolUse = {
type: "tool_use",
id: "toolu_01MNO345",
name: "ToolSearch",
input: { query: "test" },
};
const toolResult: BetaToolResultBlockParam = {
content: [
{
type: "tool_reference",
tool_name: "some_discovered_tool",
},
],
tool_use_id: "toolu_01MNO345",
is_error: false,
type: "tool_result",
};
const update = toolUpdateFromToolResult(toolResult, toolUse);
expect(update).toEqual({
content: [
{
type: "content",
content: { type: "text", text: "Tool: some_discovered_tool" },
},
],
});
});
it("should transform web_search_result content to valid ACP content", () => {
const toolUse = {
type: "tool_use",
id: "toolu_01MNO345",
name: "WebSearch",
input: { query: "test" },
};
const toolResult: BetaWebSearchToolResultBlockParam = {
content: [
{
type: "web_search_result",
title: "Test Result",
url: "https://example.com",
encrypted_content: "...",
page_age: null,
},
],
tool_use_id: "toolu_01MNO345",
type: "web_search_tool_result",
};
const update = toolUpdateFromToolResult(toolResult, toolUse);
expect(update).toEqual({
content: [
{
type: "content",
content: { type: "text", text: "Test Result (https://example.com)" },
},
],
});
});
it("should transform web_search_tool_result_error to valid ACP content", () => {
const toolUse = {
type: "tool_use",
id: "toolu_01MNO345",
name: "WebSearch",
input: { query: "test" },
};
const toolResult: BetaWebSearchToolResultBlockParam = {
content: {
type: "web_search_tool_result_error",
error_code: "unavailable",
},
tool_use_id: "toolu_01MNO345",
type: "web_search_tool_result",
};
const update = toolUpdateFromToolResult(toolResult, toolUse);
expect(update).toEqual({
content: [
{
type: "content",
content: { type: "text", text: "Error: unavailable" },
},
],
});
});
it("should transform code_execution_result content to valid ACP content", () => {
const toolUse = {
type: "tool_use",
id: "toolu_01MNO345",
name: "CodeExecution",
input: {},
};
const toolResult: BetaCodeExecutionToolResultBlockParam = {
content: {
type: "code_execution_result",
stdout: "Hello World",
stderr: "",
return_code: 0,
content: [],
},
tool_use_id: "toolu_01MNO345",
type: "code_execution_tool_result",
};
const update = toolUpdateFromToolResult(toolResult, toolUse);
expect(update).toEqual({
content: [
{
type: "content",
content: { type: "text", text: "Output: Hello World" },
},
],
});
});
it("should transform web_fetch_result content to valid ACP content", () => {
const toolUse = {
type: "tool_use",
id: "toolu_01MNO345",
name: "WebFetch",
input: { url: "https://example.com" },
};
const toolResult: BetaWebFetchToolResultBlockParam = {
content: {
type: "web_fetch_result",
url: "https://example.com",
content: {
type: "document",
citations: null,
title: null,
source: { type: "text", media_type: "text/plain", data: "Page content here" },
},
},
tool_use_id: "toolu_01MNO345",
type: "web_fetch_tool_result",
};
const update = toolUpdateFromToolResult(toolResult, toolUse);
expect(update).toEqual({
content: [
{
type: "content",
content: { type: "text", text: "Fetched: https://example.com" },
},
],
});
});
it("should transform tool_search_tool_search_result to valid ACP content", () => {
const toolUse = {
type: "tool_use",
id: "toolu_01MNO345",
name: "ToolSearch",
input: { query: "test" },
};
const toolResult: BetaToolSearchToolResultBlockParam = {
content: {
type: "tool_search_tool_search_result",
tool_references: [
{ type: "tool_reference", tool_name: "tool_a" },
{ type: "tool_reference", tool_name: "tool_b" },
],
},
tool_use_id: "toolu_01MNO345",
type: "tool_search_tool_result",
};
const update = toolUpdateFromToolResult(toolResult, toolUse);
expect(update).toEqual({
content: [
{
type: "content",
content: { type: "text", text: "Tools found: tool_a, tool_b" },
},
],
});
});
});
describe("escape markdown", () => {

View File

@@ -0,0 +1,386 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import * as fs from "node:fs";
import * as path from "node:path";
import * as os from "node:os";
import { AgentSideConnection } from "@agentclientprotocol/sdk";
import type { ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js";
describe("unstable_listSessions", () => {
let tempDir: string;
let originalClaudeConfigDir: string | undefined;
let agent: ClaudeAcpAgentType;
let ClaudeAcpAgent: typeof ClaudeAcpAgentType;
// Helper to create a mock AgentSideConnection
function createMockClient(): AgentSideConnection {
return {
sessionUpdate: async () => {},
requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),
readTextFile: async () => ({ content: "" }),
writeTextFile: async () => ({}),
} as unknown as AgentSideConnection;
}
// Helper to encode a path like Claude does:
// - Unix: "/Users/test" -> "-Users-test"
// - Windows: "C:\Users\test" -> "C-Users-test"
function encodePath(cwd: string): string {
// Handle Windows paths
if (/^[A-Za-z]:\\/.test(cwd)) {
return cwd.replace(/\\/g, "-").replace(":", "");
}
// Unix paths
return cwd.replace(/\//g, "-");
}
// Helper to write a session file
function writeSessionFile(
cwd: string,
sessionId: string,
options: {
userMessage?: string;
userMessageArray?: boolean;
mtime?: Date;
malformed?: boolean;
isAgentFile?: boolean;
} = {},
): void {
const encodedPath = encodePath(cwd);
const projectDir = path.join(tempDir, "projects", encodedPath);
fs.mkdirSync(projectDir, { recursive: true });
const filename = options.isAgentFile ? `agent-${sessionId}.jsonl` : `${sessionId}.jsonl`;
const filePath = path.join(projectDir, filename);
if (options.malformed) {
fs.writeFileSync(filePath, "not valid json\n{also bad");
return;
}
const lines: string[] = [];
// First line with sessionId
lines.push(JSON.stringify({ sessionId, type: "init" }));
// User message if provided
if (options.userMessage) {
const content = options.userMessageArray
? [{ type: "text", text: options.userMessage }]
: options.userMessage;
lines.push(JSON.stringify({ type: "user", message: { content } }));
}
// Assistant response
lines.push(JSON.stringify({ type: "assistant", message: { content: "Hello!" } }));
fs.writeFileSync(filePath, lines.join("\n"));
// Set modification time if specified
if (options.mtime) {
fs.utimesSync(filePath, options.mtime, options.mtime);
}
}
beforeEach(async () => {
// Create temp directory to isolate tests from real filesystem.
// We set process.env.CLAUDE_CONFIG_DIR before importing the module so that
// CLAUDE_CONFIG_DIR is evaluated with our temp directory.
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "claude-test-"));
originalClaudeConfigDir = process.env.CLAUDE_CONFIG_DIR;
process.env.CLAUDE_CONFIG_DIR = tempDir;
// Reset modules to pick up the new CLAUDE_CONFIG_DIR env var
vi.resetModules();
// Dynamic import after setting env var
const acpAgent = await import("../acp-agent.js");
ClaudeAcpAgent = acpAgent.ClaudeAcpAgent;
// Create agent instance
agent = new ClaudeAcpAgent(createMockClient());
});
afterEach(() => {
// Restore env and cleanup
if (originalClaudeConfigDir !== undefined) {
process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir;
} else {
delete process.env.CLAUDE_CONFIG_DIR;
}
fs.rmSync(tempDir, { recursive: true, force: true });
});
it("returns empty array when projects directory does not exist", async () => {
const result = await agent.unstable_listSessions({});
expect(result.sessions).toEqual([]);
expect(result.nextCursor).toBeUndefined();
});
it("returns empty array when projects directory is empty", async () => {
fs.mkdirSync(path.join(tempDir, "projects"), { recursive: true });
const result = await agent.unstable_listSessions({});
expect(result.sessions).toEqual([]);
});
it("parses session files and returns correct metadata", async () => {
const cwd = "/Users/test/myproject";
const sessionId = "sess-123";
const mtime = new Date("2025-01-15T10:30:00Z");
writeSessionFile(cwd, sessionId, {
userMessage: "Hello, can you help me?",
mtime,
});
const result = await agent.unstable_listSessions({});
expect(result.sessions).toHaveLength(1);
expect(result.sessions[0]).toMatchObject({
sessionId: "sess-123",
cwd: "/Users/test/myproject",
title: "Hello, can you help me?",
});
expect(result.sessions[0]!.updatedAt).toBeDefined();
});
it("extracts title from array-style user message content", async () => {
writeSessionFile("/Users/test/project", "sess-456", {
userMessage: "Fix the bug in auth",
userMessageArray: true,
});
const result = await agent.unstable_listSessions({});
expect(result.sessions[0]!.title).toBe("Fix the bug in auth");
});
it("truncates long titles to 128 characters with ellipsis", async () => {
const longMessage = "A".repeat(150);
writeSessionFile("/Users/test/project", "sess-789", {
userMessage: longMessage,
});
const result = await agent.unstable_listSessions({});
expect(result.sessions[0]!.title).toBe("A".repeat(127) + "…");
});
it("replaces newlines with spaces in titles", async () => {
const messageWithNewlines = "First line\nSecond line\r\nThird line";
writeSessionFile("/Users/test/project", "sess-newlines", {
userMessage: messageWithNewlines,
});
const result = await agent.unstable_listSessions({});
expect(result.sessions[0]!.title).toBe("First line Second line Third line");
});
it("collapses multiple whitespace characters in titles", async () => {
const messageWithWhitespace = "Hello world \n\n test";
writeSessionFile("/Users/test/project", "sess-whitespace", {
userMessage: messageWithWhitespace,
});
const result = await agent.unstable_listSessions({});
expect(result.sessions[0]!.title).toBe("Hello world test");
});
it("filters sessions by cwd parameter", async () => {
writeSessionFile("/Users/test/projectone", "sess-a", { userMessage: "Project A" });
writeSessionFile("/Users/test/projecttwo", "sess-b", { userMessage: "Project B" });
const result = await agent.unstable_listSessions({ cwd: "/Users/test/projectone" });
expect(result.sessions).toHaveLength(1);
expect(result.sessions[0]!.sessionId).toBe("sess-a");
});
it("returns all sessions when cwd is not specified", async () => {
writeSessionFile("/Users/test/projectone", "sess-a", { userMessage: "A" });
writeSessionFile("/Users/test/projecttwo", "sess-b", { userMessage: "B" });
const result = await agent.unstable_listSessions({});
expect(result.sessions).toHaveLength(2);
});
it("sorts sessions by updatedAt descending (most recent first)", async () => {
writeSessionFile("/Users/test/project", "old-session", {
userMessage: "Old",
mtime: new Date("2025-01-01T00:00:00Z"),
});
writeSessionFile("/Users/test/project", "new-session", {
userMessage: "New",
mtime: new Date("2025-01-15T00:00:00Z"),
});
const result = await agent.unstable_listSessions({});
expect(result.sessions[0]!.sessionId).toBe("new-session");
expect(result.sessions[1]!.sessionId).toBe("old-session");
});
it("skips files starting with agent-", async () => {
writeSessionFile("/Users/test/project", "regular-session", { userMessage: "Regular" });
writeSessionFile("/Users/test/project", "hidden-session", {
userMessage: "Hidden",
isAgentFile: true,
});
const result = await agent.unstable_listSessions({});
expect(result.sessions).toHaveLength(1);
expect(result.sessions[0]!.sessionId).toBe("regular-session");
});
it("skips malformed session files gracefully", async () => {
writeSessionFile("/Users/test/project", "good-session", { userMessage: "Good" });
writeSessionFile("/Users/test/project", "bad-session", { malformed: true });
const result = await agent.unstable_listSessions({});
expect(result.sessions).toHaveLength(1);
expect(result.sessions[0]!.sessionId).toBe("good-session");
});
it("uses filename as sessionId when not in file content", async () => {
const cwd = "/Users/test/project";
const encodedPath = encodePath(cwd);
const projectDir = path.join(tempDir, "projects", encodedPath);
fs.mkdirSync(projectDir, { recursive: true });
// Write file without sessionId in content
const filePath = path.join(projectDir, "fallback-id.jsonl");
fs.writeFileSync(filePath, JSON.stringify({ type: "init" }) + "\n");
const result = await agent.unstable_listSessions({});
expect(result.sessions[0]!.sessionId).toBe("fallback-id");
});
it("returns null title when no user message exists", async () => {
const cwd = "/Users/test/project";
const encodedPath = encodePath(cwd);
const projectDir = path.join(tempDir, "projects", encodedPath);
fs.mkdirSync(projectDir, { recursive: true });
// Write file with only init and assistant message
const filePath = path.join(projectDir, "no-user.jsonl");
fs.writeFileSync(
filePath,
[
JSON.stringify({ sessionId: "no-user", type: "init" }),
JSON.stringify({ type: "assistant", message: { content: "Hi" } }),
].join("\n"),
);
const result = await agent.unstable_listSessions({});
expect(result.sessions[0]!.title).toBeNull();
});
describe("pagination", () => {
it("returns nextCursor when there are more results", async () => {
// Create 60 sessions (more than PAGE_SIZE of 50)
for (let i = 0; i < 60; i++) {
writeSessionFile("/Users/test/project", `sess-${i.toString().padStart(3, "0")}`, {
userMessage: `Session ${i}`,
mtime: new Date(Date.now() - i * 1000), // Each older by 1 second
});
}
const result = await agent.unstable_listSessions({});
expect(result.sessions).toHaveLength(50);
expect(result.nextCursor).toBeDefined();
});
it("does not return nextCursor when all results fit in one page", async () => {
for (let i = 0; i < 10; i++) {
writeSessionFile("/Users/test/project", `sess-${i}`, { userMessage: `Session ${i}` });
}
const result = await agent.unstable_listSessions({});
expect(result.sessions).toHaveLength(10);
expect(result.nextCursor).toBeUndefined();
});
it("returns next page when cursor is provided", async () => {
// Create 60 sessions
for (let i = 0; i < 60; i++) {
writeSessionFile("/Users/test/project", `sess-${i.toString().padStart(3, "0")}`, {
userMessage: `Session ${i}`,
mtime: new Date(Date.now() - i * 1000),
});
}
// Get first page
const firstPage = await agent.unstable_listSessions({});
expect(firstPage.sessions).toHaveLength(50);
expect(firstPage.nextCursor).toBeDefined();
// Get second page
const secondPage = await agent.unstable_listSessions({ cursor: firstPage.nextCursor });
expect(secondPage.sessions).toHaveLength(10);
expect(secondPage.nextCursor).toBeUndefined();
});
it("handles invalid cursor gracefully (starts from beginning)", async () => {
writeSessionFile("/Users/test/project", "sess-1", { userMessage: "Test" });
const result = await agent.unstable_listSessions({ cursor: "invalid-base64!" });
expect(result.sessions).toHaveLength(1);
});
it("handles malformed cursor JSON gracefully", async () => {
writeSessionFile("/Users/test/project", "sess-1", { userMessage: "Test" });
// Valid base64 but not valid JSON
const badCursor = Buffer.from("not json").toString("base64");
const result = await agent.unstable_listSessions({ cursor: badCursor });
expect(result.sessions).toHaveLength(1);
});
});
describe("Windows path support", () => {
it("decodes Windows-style paths correctly", async () => {
// Simulate a Windows path encoded as "C-Users-test-project"
const windowsCwd = "C:\\Users\\test\\project";
writeSessionFile(windowsCwd, "win-session", { userMessage: "Windows test" });
const result = await agent.unstable_listSessions({});
expect(result.sessions).toHaveLength(1);
expect(result.sessions[0]!.cwd).toBe("C:\\Users\\test\\project");
expect(result.sessions[0]!.sessionId).toBe("win-session");
});
it("filters Windows paths by cwd correctly", async () => {
writeSessionFile("C:\\Users\\test\\projectone", "win-a", { userMessage: "A" });
writeSessionFile("C:\\Users\\test\\projecttwo", "win-b", { userMessage: "B" });
const result = await agent.unstable_listSessions({ cwd: "C:\\Users\\test\\projectone" });
expect(result.sessions).toHaveLength(1);
expect(result.sessions[0]!.sessionId).toBe("win-a");
});
it("handles mixed Unix and Windows paths", async () => {
writeSessionFile("/Users/test/unixproject", "unix-session", { userMessage: "Unix" });
writeSessionFile("C:\\Users\\test\\winproject", "win-session", { userMessage: "Windows" });
const result = await agent.unstable_listSessions({});
expect(result.sessions).toHaveLength(2);
const cwds = result.sessions.map((s) => s.cwd);
expect(cwds).toContain("/Users/test/unixproject");
expect(cwds).toContain("C:\\Users\\test\\winproject");
});
});
});

318
src/tests/tools.test.ts Normal file
View File

@@ -0,0 +1,318 @@
import { describe, it, expect } from "vitest";
import { AgentSideConnection } from "@agentclientprotocol/sdk";
import { ToolResultBlockParam } from "@anthropic-ai/sdk/resources";
import {
BetaMCPToolResultBlock,
BetaTextBlock,
BetaWebSearchResultBlock,
BetaWebSearchToolResultBlock,
BetaBashCodeExecutionToolResultBlock,
BetaBashCodeExecutionResultBlock,
} from "@anthropic-ai/sdk/resources/beta.mjs";
import { toAcpNotifications, ToolUseCache, Logger } from "../acp-agent.js";
describe("rawOutput in tool call updates", () => {
const mockClient = {} as AgentSideConnection;
const mockLogger: Logger = { log: () => {}, error: () => {} };
it("should include rawOutput with string content for tool_result", () => {
const toolUseCache: ToolUseCache = {
toolu_123: {
type: "tool_use",
id: "toolu_123",
name: "Bash",
input: { command: "echo hello" },
},
};
const toolResult: ToolResultBlockParam = {
type: "tool_result",
tool_use_id: "toolu_123",
content: "hello\n",
is_error: false,
};
const notifications = toAcpNotifications(
[toolResult],
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
);
expect(notifications).toHaveLength(1);
expect(notifications[0].update).toMatchObject({
sessionUpdate: "tool_call_update",
toolCallId: "toolu_123",
status: "completed",
rawOutput: "hello\n",
});
});
it("should include rawOutput with array content for tool_result", () => {
const toolUseCache: ToolUseCache = {
toolu_456: {
type: "tool_use",
id: "toolu_456",
name: "Read",
input: { file_path: "/test/file.txt" },
},
};
// ToolResultBlockParam content can be string or array of TextBlockParam
const toolResult: ToolResultBlockParam = {
type: "tool_result",
tool_use_id: "toolu_456",
content: [{ type: "text", text: "Line 1\nLine 2\nLine 3" }],
is_error: false,
};
const notifications = toAcpNotifications(
[toolResult],
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
);
expect(notifications).toHaveLength(1);
expect(notifications[0].update).toMatchObject({
sessionUpdate: "tool_call_update",
toolCallId: "toolu_456",
status: "completed",
rawOutput: [{ type: "text", text: "Line 1\nLine 2\nLine 3" }],
});
});
it("should include rawOutput for mcp_tool_result with string content", () => {
const toolUseCache: ToolUseCache = {
toolu_789: {
type: "tool_use",
id: "toolu_789",
name: "mcp__server__tool",
input: { query: "test" },
},
};
// BetaMCPToolResultBlock content can be string or Array<BetaTextBlock>
const toolResult: BetaMCPToolResultBlock = {
type: "mcp_tool_result",
tool_use_id: "toolu_789",
content: '{"result": "success", "data": [1, 2, 3]}',
is_error: false,
};
const notifications = toAcpNotifications(
[toolResult],
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
);
expect(notifications).toHaveLength(1);
expect(notifications[0].update).toMatchObject({
sessionUpdate: "tool_call_update",
toolCallId: "toolu_789",
status: "completed",
rawOutput: '{"result": "success", "data": [1, 2, 3]}',
});
});
it("should include rawOutput for mcp_tool_result with array content", () => {
const toolUseCache: ToolUseCache = {
toolu_abc: {
type: "tool_use",
id: "toolu_abc",
name: "mcp__server__search",
input: { term: "test" },
},
};
// BetaTextBlock requires citations field
const arrayContent: BetaTextBlock[] = [
{ type: "text", text: "Result 1", citations: null },
{ type: "text", text: "Result 2", citations: null },
];
const toolResult: BetaMCPToolResultBlock = {
type: "mcp_tool_result",
tool_use_id: "toolu_abc",
content: arrayContent,
is_error: false,
};
const notifications = toAcpNotifications(
[toolResult],
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
);
expect(notifications).toHaveLength(1);
expect(notifications[0].update).toMatchObject({
sessionUpdate: "tool_call_update",
toolCallId: "toolu_abc",
status: "completed",
rawOutput: arrayContent,
});
});
it("should include rawOutput for web_search_tool_result", () => {
const toolUseCache: ToolUseCache = {
toolu_web: {
type: "tool_use",
id: "toolu_web",
name: "WebSearch",
input: { query: "test search" },
},
};
// BetaWebSearchResultBlock from SDK
const searchResults: BetaWebSearchResultBlock[] = [
{
type: "web_search_result",
url: "https://example.com",
title: "Example",
encrypted_content: "encrypted content here",
page_age: "2 days ago",
},
];
const toolResult: BetaWebSearchToolResultBlock = {
type: "web_search_tool_result",
tool_use_id: "toolu_web",
content: searchResults,
};
const notifications = toAcpNotifications(
[toolResult],
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
);
expect(notifications).toHaveLength(1);
expect(notifications[0].update).toMatchObject({
sessionUpdate: "tool_call_update",
toolCallId: "toolu_web",
status: "completed",
rawOutput: searchResults,
});
});
it("should include rawOutput for bash_code_execution_tool_result", () => {
const toolUseCache: ToolUseCache = {
toolu_bash: {
type: "tool_use",
id: "toolu_bash",
name: "Bash",
input: { command: "ls -la" },
},
};
// BetaBashCodeExecutionResultBlock from SDK
const bashResult: BetaBashCodeExecutionResultBlock = {
type: "bash_code_execution_result",
stdout: "file1.txt\nfile2.txt",
stderr: "",
return_code: 0,
content: [],
};
const toolResult: BetaBashCodeExecutionToolResultBlock = {
type: "bash_code_execution_tool_result",
tool_use_id: "toolu_bash",
content: bashResult,
};
const notifications = toAcpNotifications(
[toolResult],
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
);
expect(notifications).toHaveLength(1);
expect(notifications[0].update).toMatchObject({
sessionUpdate: "tool_call_update",
toolCallId: "toolu_bash",
status: "completed",
rawOutput: bashResult,
});
});
it("should set status to failed when is_error is true", () => {
const toolUseCache: ToolUseCache = {
toolu_err: {
type: "tool_use",
id: "toolu_err",
name: "Bash",
input: { command: "invalid_command" },
},
};
const toolResult: ToolResultBlockParam = {
type: "tool_result",
tool_use_id: "toolu_err",
content: "command not found: invalid_command",
is_error: true,
};
const notifications = toAcpNotifications(
[toolResult],
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
);
expect(notifications).toHaveLength(1);
expect(notifications[0].update).toMatchObject({
sessionUpdate: "tool_call_update",
toolCallId: "toolu_err",
status: "failed",
rawOutput: "command not found: invalid_command",
});
});
it("should not emit tool_call_update for TodoWrite (emits plan instead)", () => {
const toolUseCache: ToolUseCache = {
toolu_todo: {
type: "tool_use",
id: "toolu_todo",
name: "TodoWrite",
input: { todos: [{ content: "Test task", status: "pending" }] },
},
};
const toolResult: ToolResultBlockParam = {
type: "tool_result",
tool_use_id: "toolu_todo",
content: "Todos updated successfully",
is_error: false,
};
const notifications = toAcpNotifications(
[toolResult],
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
);
// TodoWrite should not emit tool_call_update - it emits plan updates instead
expect(notifications).toHaveLength(0);
});
});

View File

@@ -1,7 +1,44 @@
import { PlanEntry, ToolCallContent, ToolCallLocation, ToolKind } from "@agentclientprotocol/sdk";
import {
ContentBlock,
PlanEntry,
ToolCallContent,
ToolCallLocation,
ToolKind,
} from "@agentclientprotocol/sdk";
import { SYSTEM_REMINDER } from "./mcp-server.js";
import * as diff from "diff";
import { ToolResultBlockParam, WebSearchToolResultBlockParam } from "@anthropic-ai/sdk/resources";
import {
ImageBlockParam,
TextBlockParam,
ToolResultBlockParam,
WebSearchResultBlock,
WebSearchToolResultBlockParam,
WebSearchToolResultError,
} from "@anthropic-ai/sdk/resources";
import {
BetaBashCodeExecutionToolResultBlockParam,
BetaBashCodeExecutionResultBlock,
BetaBashCodeExecutionToolResultError,
BetaCodeExecutionToolResultBlockParam,
BetaCodeExecutionResultBlock,
BetaCodeExecutionToolResultError,
BetaRequestMCPToolResultBlockParam,
BetaTextEditorCodeExecutionToolResultBlockParam,
BetaTextEditorCodeExecutionViewResultBlock,
BetaTextEditorCodeExecutionCreateResultBlock,
BetaTextEditorCodeExecutionStrReplaceResultBlock,
BetaTextEditorCodeExecutionToolResultError,
BetaToolResultBlockParam,
BetaToolSearchToolResultBlockParam,
BetaToolReferenceBlock,
BetaToolSearchToolSearchResultBlock,
BetaToolSearchToolResultError,
BetaWebFetchToolResultBlockParam,
BetaWebFetchBlock,
BetaWebFetchToolResultErrorBlock,
BetaWebSearchToolResultBlockParam,
BetaImageBlockParam,
} from "@anthropic-ai/sdk/resources/beta.mjs";
const acpUnqualifiedToolNames = {
read: "Read",
@@ -24,15 +61,29 @@ export const acpToolNames = {
export const EDIT_TOOL_NAMES = [acpToolNames.edit, acpToolNames.write];
import {
BetaBashCodeExecutionToolResultBlockParam,
BetaCodeExecutionToolResultBlockParam,
BetaRequestMCPToolResultBlockParam,
BetaTextEditorCodeExecutionToolResultBlockParam,
BetaToolSearchToolResultBlockParam,
BetaWebFetchToolResultBlockParam,
BetaWebSearchToolResultBlockParam,
} from "@anthropic-ai/sdk/resources/beta.mjs";
/**
* Union of all possible content types that can appear in tool results from the Anthropic SDK.
* These are transformed to valid ACP ContentBlock types by toValidAcpContent().
*/
type ToolResultContent =
| TextBlockParam
| ImageBlockParam
| BetaImageBlockParam
| BetaToolReferenceBlock
| BetaToolSearchToolSearchResultBlock
| BetaToolSearchToolResultError
| WebSearchResultBlock
| WebSearchToolResultError
| BetaWebFetchBlock
| BetaWebFetchToolResultErrorBlock
| BetaCodeExecutionResultBlock
| BetaCodeExecutionToolResultError
| BetaBashCodeExecutionResultBlock
| BetaBashCodeExecutionToolResultError
| BetaTextEditorCodeExecutionViewResultBlock
| BetaTextEditorCodeExecutionCreateResultBlock
| BetaTextEditorCodeExecutionStrReplaceResultBlock
| BetaTextEditorCodeExecutionToolResultError;
import { HookCallback } from "@anthropic-ai/claude-agent-sdk";
import { Logger } from "./acp-agent.js";
import { SettingsManager } from "./settings.js";
@@ -406,6 +457,7 @@ export function toolInfoFromToolUse(toolUse: any): ToolInfo {
export function toolUpdateFromToolResult(
toolResult:
| ToolResultBlockParam
| BetaToolResultBlockParam
| BetaWebSearchToolResultBlockParam
| BetaWebFetchToolResultBlockParam
| WebSearchToolResultBlockParam
@@ -547,17 +599,20 @@ function toAcpContentUpdate(
): { content?: ToolCallContent[] } {
if (Array.isArray(content) && content.length > 0) {
return {
content: content.map((content: any) => ({
type: "content",
content:
isError && content.type === "text"
? {
...content,
text: `\`\`\`\n${content.text}\n\`\`\``,
}
: content,
content: content.map((c: any) => ({
type: "content" as const,
content: toAcpContentBlock(c, isError),
})),
};
} else if (typeof content === "object" && content !== null && "type" in content) {
return {
content: [
{
type: "content" as const,
content: toAcpContentBlock(content, isError),
},
],
};
} else if (typeof content === "string" && content.length > 0) {
return {
content: [
@@ -574,6 +629,74 @@ function toAcpContentUpdate(
return {};
}
function toAcpContentBlock(content: ToolResultContent, isError: boolean): ContentBlock {
const wrapText = (text: string): ContentBlock => ({
type: "text" as const,
text: isError ? `\`\`\`\n${text}\n\`\`\`` : text,
});
switch (content.type) {
case "text":
return {
type: "text" as const,
text: isError ? `\`\`\`\n${content.text}\n\`\`\`` : content.text,
};
case "image":
if (content.source.type === "base64") {
return {
type: "image" as const,
data: content.source.data,
mimeType: content.source.media_type,
};
}
// URL and file-based images can't be converted to ACP format (requires data)
return wrapText(
content.source.type === "url"
? `[image: ${content.source.url}]`
: "[image: file reference]",
);
case "tool_reference":
return wrapText(`Tool: ${content.tool_name}`);
case "tool_search_tool_search_result":
return wrapText(
`Tools found: ${content.tool_references.map((r) => r.tool_name).join(", ") || "none"}`,
);
case "tool_search_tool_result_error":
return wrapText(
`Error: ${content.error_code}${content.error_message ? ` - ${content.error_message}` : ""}`,
);
case "web_search_result":
return wrapText(`${content.title} (${content.url})`);
case "web_search_tool_result_error":
return wrapText(`Error: ${content.error_code}`);
case "web_fetch_result":
return wrapText(`Fetched: ${content.url}`);
case "web_fetch_tool_result_error":
return wrapText(`Error: ${content.error_code}`);
case "code_execution_result":
return wrapText(`Output: ${content.stdout || content.stderr || ""}`);
case "bash_code_execution_result":
return wrapText(`Output: ${content.stdout || content.stderr || ""}`);
case "code_execution_tool_result_error":
case "bash_code_execution_tool_result_error":
return wrapText(`Error: ${content.error_code}`);
case "text_editor_code_execution_view_result":
return wrapText(content.content);
case "text_editor_code_execution_create_result":
return wrapText(content.is_file_update ? "File updated" : "File created");
case "text_editor_code_execution_str_replace_result":
return wrapText(content.lines?.join("\n") || "");
case "text_editor_code_execution_tool_result_error":
return wrapText(
`Error: ${content.error_code}${content.error_message ? ` - ${content.error_message}` : ""}`,
);
default:
return wrapText(JSON.stringify(content));
}
}
export type ClaudePlanEntry = {
content: string;
status: "pending" | "in_progress" | "completed";
@@ -629,16 +752,28 @@ export const registerHookCallback = (
/* A callback for Claude Code that is called when receiving a PostToolUse hook */
export const createPostToolUseHook =
(logger: Logger = console): HookCallback =>
(
logger: Logger = console,
options?: {
onEnterPlanMode?: () => Promise<void>;
},
): HookCallback =>
async (input: any, toolUseID: string | undefined): Promise<{ continue: boolean }> => {
if (input.hook_event_name === "PostToolUse" && toolUseID) {
const onPostToolUseHook = toolUseCallbacks[toolUseID]?.onPostToolUseHook;
if (onPostToolUseHook) {
await onPostToolUseHook(toolUseID, input.tool_input, input.tool_response);
delete toolUseCallbacks[toolUseID]; // Cleanup after execution
} else {
logger.error(`No onPostToolUseHook found for tool use ID: ${toolUseID}`);
delete toolUseCallbacks[toolUseID];
if (input.hook_event_name === "PostToolUse") {
// Handle EnterPlanMode tool - notify client of mode change after successful execution
if (input.tool_name === "EnterPlanMode" && options?.onEnterPlanMode) {
await options.onEnterPlanMode();
}
if (toolUseID) {
const onPostToolUseHook = toolUseCallbacks[toolUseID]?.onPostToolUseHook;
if (onPostToolUseHook) {
await onPostToolUseHook(toolUseID, input.tool_input, input.tool_response);
delete toolUseCallbacks[toolUseID]; // Cleanup after execution
} else {
logger.error(`No onPostToolUseHook found for tool use ID: ${toolUseID}`);
delete toolUseCallbacks[toolUseID];
}
}
}
return { continue: true };