From 7b9717a79aadd3739059f90f6102821640ef502c Mon Sep 17 00:00:00 2001 From: Scion Agent Date: Tue, 7 Jul 2026 04:55:07 +0000 Subject: [PATCH] feat: add A2A protocol channel for agent-to-agent communication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds @flue/a2a — a channel adapter that makes Flue agents speak the A2A (Agent-to-Agent) protocol for inter-agent discovery and messaging. Features: - Agent Card serving at /.well-known/agent-card.json - message/send JSON-RPC handler with task lifecycle - Skill mapping from Flue skills to A2A descriptors - protocolVersion 1.0 compliant The A2A protocol (https://a2a-protocol.org/) enables interoperability between AI agents across different frameworks and platforms. This channel lets any Flue agent participate in A2A ecosystems. --- packages/a2a/README.md | 65 ++ packages/a2a/package-lock.json | 1784 ++++++++++++++++++++++++++++++++ packages/a2a/package.json | 40 + packages/a2a/src/errors.ts | 16 + packages/a2a/src/handlers.ts | 406 ++++++++ packages/a2a/src/index.ts | 417 ++++++++ packages/a2a/src/types.ts | 107 ++ packages/a2a/tsconfig.json | 5 + packages/a2a/tsdown.config.ts | 8 + 9 files changed, 2848 insertions(+) create mode 100644 packages/a2a/README.md create mode 100644 packages/a2a/package-lock.json create mode 100644 packages/a2a/package.json create mode 100644 packages/a2a/src/errors.ts create mode 100644 packages/a2a/src/handlers.ts create mode 100644 packages/a2a/src/index.ts create mode 100644 packages/a2a/src/types.ts create mode 100644 packages/a2a/tsconfig.json create mode 100644 packages/a2a/tsdown.config.ts diff --git a/packages/a2a/README.md b/packages/a2a/README.md new file mode 100644 index 000000000..882a1b384 --- /dev/null +++ b/packages/a2a/README.md @@ -0,0 +1,65 @@ +# @flue/a2a — Agent-to-Agent Protocol Channel + +A Flue channel adapter that makes any Flue agent speak the [A2A protocol](https://a2a-protocol.org/), enabling agent-to-agent communication and discovery. + +## What this does + +- **Agent Card**: serves `/.well-known/agent-card.json` for A2A discovery +- **message/send**: accepts A2A JSON-RPC messages and routes them to the Flue agent +- **Task lifecycle**: tracks task state (submitted → working → completed) +- **Skills**: maps Flue skills to A2A skill descriptors + +## Quick Start + +```typescript +import { createA2AChannel } from '@flue/a2a'; + +const channel = createA2AChannel({ + name: 'My Agent', + description: 'A helpful assistant', + port: 3000, +}); + +// Agent is now discoverable via A2A protocol +// GET /.well-known/agent-card.json +// POST / (message/send) +``` + +## A2A Protocol + +The [Agent-to-Agent (A2A) protocol](https://a2a-protocol.org/latest/specification/) enables interoperability between AI agents. Key concepts: + +- **Agent Cards**: JSON metadata describing an agent's capabilities, served at a well-known URL +- **message/send**: JSON-RPC method for sending messages between agents +- **Tasks**: Units of work with lifecycle states (submitted, working, completed, failed) +- **Skills**: Declared capabilities that clients can discover and invoke + +## Configuration + +| Option | Type | Description | +|--------|------|-------------| +| `name` | string | Agent display name | +| `description` | string | Agent description | +| `port` | number | HTTP server port (default: 3000) | +| `skills` | Skill[] | A2A skill descriptors | +| `version` | string | Agent version | +| `protocolVersion` | string | A2A protocol version (default: "1.0") | + +## Testing + +```bash +# Start the agent +npm start + +# Discover the agent +curl http://localhost:3000/.well-known/agent-card.json + +# Send a message +curl -X POST http://localhost:3000/ \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"message/send","params":{"message":{"messageId":"1","role":"user","parts":[{"kind":"text","text":"Hello"}]}},"id":"1"}' +``` + +## License + +MIT diff --git a/packages/a2a/package-lock.json b/packages/a2a/package-lock.json new file mode 100644 index 000000000..553e85ac8 --- /dev/null +++ b/packages/a2a/package-lock.json @@ -0,0 +1,1784 @@ +{ + "name": "@flue/a2a", + "version": "1.0.0-beta.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@flue/a2a", + "version": "1.0.0-beta.1", + "license": "Apache-2.0", + "dependencies": { + "@a2a-js/sdk": "^1.0.0-beta.0", + "hono": "4.12.22" + }, + "devDependencies": { + "tsdown": "^0.22.0", + "typescript": "^6.0.3", + "vitest": "^4.1.6" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@a2a-js/sdk": { + "version": "1.0.0-beta.0", + "resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-1.0.0-beta.0.tgz", + "integrity": "sha512-1k2GJdATg0ZJMNVD0jqox+oE5teUaUbJWx5ooSW2i3hA/B00Ev6kK3hZuxcsoZYbYvp8eAyppU3rvjTpglzdcg==", + "license": "Apache-2.0", + "dependencies": { + "jose": "^6.2.3", + "uuid": "^11.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.10.2", + "@grpc/grpc-js": "^1.11.0", + "express": "^4.21.2 || ^5.1.0" + }, + "peerDependenciesMeta": { + "@bufbuild/protobuf": { + "optional": true + }, + "@grpc/grpc-js": { + "optional": true + }, + "express": { + "optional": true + } + } + }, + "node_modules/@babel/generator": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.2.tgz", + "integrity": "sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0.tgz", + "integrity": "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/types": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0.tgz", + "integrity": "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@quansync/fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz", + "integrity": "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", + "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", + "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", + "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", + "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", + "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", + "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", + "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", + "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", + "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", + "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", + "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", + "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", + "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", + "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", + "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", + "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", + "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.9", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", + "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "@vitest/utils": "4.1.9", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", + "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ansis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-kit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-3.0.0.tgz", + "integrity": "sha512-8OG92q3R35qjC/4i6BLBMg8IB+fClWu/1PEwg2Z9Rn+BuNaiEgJzpzn+pxWOdHJWDCAwu2JP0wCDTozAM4QirQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0", + "estree-walker": "^3.0.3", + "pathe": "^2.0.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/birpc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-4.0.0.tgz", + "integrity": "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dts-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-3.0.0.tgz", + "integrity": "sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "oxc-resolver": ">=11.0.0" + }, + "peerDependenciesMeta": { + "oxc-resolver": { + "optional": true + } + } + }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.5.tgz", + "integrity": "sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/hono": { + "version": "4.12.22", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.22.tgz", + "integrity": "sha512-7fvVPbB92zNRsQke+uiRGwtTuef0tB2Dg4hWxYfFNvkQhIltWoyi0ONReM5LWA+jJWS3nfT5lTq+qbsIpX0IQw==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/hookable": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", + "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-without-cache": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/import-without-cache/-/import-without-cache-0.4.0.tgz", + "integrity": "sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/quansync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rolldown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", + "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.137.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.3", + "@rolldown/binding-darwin-arm64": "1.1.3", + "@rolldown/binding-darwin-x64": "1.1.3", + "@rolldown/binding-freebsd-x64": "1.1.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", + "@rolldown/binding-linux-arm64-gnu": "1.1.3", + "@rolldown/binding-linux-arm64-musl": "1.1.3", + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", + "@rolldown/binding-linux-s390x-gnu": "1.1.3", + "@rolldown/binding-linux-x64-gnu": "1.1.3", + "@rolldown/binding-linux-x64-musl": "1.1.3", + "@rolldown/binding-openharmony-arm64": "1.1.3", + "@rolldown/binding-wasm32-wasi": "1.1.3", + "@rolldown/binding-win32-arm64-msvc": "1.1.3", + "@rolldown/binding-win32-x64-msvc": "1.1.3" + } + }, + "node_modules/rolldown-plugin-dts": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.26.0.tgz", + "integrity": "sha512-e+kEPtUiDES0htk5iqkSeF4EzAV7R+vugGB44iPDuw1Kw9E+WyL1VG7PaV0IIjGHLiacztMBcMTyrr8ON9CT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.0", + "@babel/parser": "^8.0.0", + "ast-kit": "^3.0.0", + "birpc": "^4.0.0", + "dts-resolver": "^3.0.0", + "get-tsconfig": "5.0.0-beta.5", + "obug": "^2.1.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@ts-macro/tsc": "^0.3.6", + "@typescript/native-preview": ">=7.0.0-dev.20260325.1", + "rolldown": "^1.0.0", + "typescript": "^5.0.0 || ^6.0.0", + "vue-tsc": "~3.2.0 || ~3.3.0" + }, + "peerDependenciesMeta": { + "@ts-macro/tsc": { + "optional": true + }, + "@typescript/native-preview": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tsdown": { + "version": "0.22.3", + "resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.22.3.tgz", + "integrity": "sha512-louqbfA8Qf//B9jTTL0FPtXTNpjCWv1VPkbcmQMph2pTpzs+LnB1tbe4tDDRVpo2BjF5SgUXaTZe45SxB8pWHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.3.1", + "cac": "^7.0.0", + "defu": "^6.1.7", + "empathic": "^2.0.1", + "hookable": "^6.1.1", + "import-without-cache": "^0.4.0", + "obug": "^2.1.3", + "picomatch": "^4.0.4", + "rolldown": "~1.1.1", + "rolldown-plugin-dts": "^0.26.0", + "semver": "^7.8.4", + "tinyexec": "^1.2.4", + "tinyglobby": "^0.2.17", + "tree-kill": "^1.2.2", + "unconfig-core": "^7.5.0" + }, + "bin": { + "tsdown": "dist/run.mjs" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@arethetypeswrong/core": "^0.18.1", + "@tsdown/css": "0.22.3", + "@tsdown/exe": "0.22.3", + "@vitejs/devtools": "*", + "publint": "^0.3.8", + "tsx": "*", + "typescript": "^5.0.0 || ^6.0.0", + "unplugin-unused": "^0.5.0", + "unrun": "*" + }, + "peerDependenciesMeta": { + "@arethetypeswrong/core": { + "optional": true + }, + "@tsdown/css": { + "optional": true + }, + "@tsdown/exe": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "publint": { + "optional": true + }, + "tsx": { + "optional": true + }, + "typescript": { + "optional": true + }, + "unplugin-unused": { + "optional": true + }, + "unrun": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unconfig-core": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.5.0.tgz", + "integrity": "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^1.0.0", + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vite": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", + "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "~1.1.2", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", + "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.9", + "@vitest/mocker": "4.1.9", + "@vitest/pretty-format": "4.1.9", + "@vitest/runner": "4.1.9", + "@vitest/snapshot": "4.1.9", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.9", + "@vitest/browser-preview": "4.1.9", + "@vitest/browser-webdriverio": "4.1.9", + "@vitest/coverage-istanbul": "4.1.9", + "@vitest/coverage-v8": "4.1.9", + "@vitest/ui": "4.1.9", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/packages/a2a/package.json b/packages/a2a/package.json new file mode 100644 index 000000000..1d4d266a4 --- /dev/null +++ b/packages/a2a/package.json @@ -0,0 +1,40 @@ +{ + "name": "@flue/a2a", + "version": "1.0.0-beta.1", + "type": "module", + "license": "Apache-2.0", + "homepage": "https://flueframework.com/", + "repository": { + "type": "git", + "url": "https://github.com/withastro/flue", + "directory": "packages/a2a" + }, + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + } + }, + "main": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "files": [ + "dist" + ], + "engines": { + "node": ">=22.19.0" + }, + "scripts": { + "build": "tsdown", + "check:types": "tsc --noEmit", + "test": "vitest run" + }, + "dependencies": { + "@a2a-js/sdk": "^1.0.0-beta.0", + "hono": "4.12.22" + }, + "devDependencies": { + "tsdown": "^0.22.0", + "typescript": "^6.0.3", + "vitest": "^4.1.6" + } +} diff --git a/packages/a2a/src/errors.ts b/packages/a2a/src/errors.ts new file mode 100644 index 000000000..81621bf8a --- /dev/null +++ b/packages/a2a/src/errors.ts @@ -0,0 +1,16 @@ +export class InvalidA2AConversationKeyError extends Error { + constructor() { + super('Invalid A2A conversation key.'); + this.name = 'InvalidA2AConversationKeyError'; + } +} + +export class InvalidA2AInputError extends TypeError { + readonly field: string; + + constructor(field: string) { + super(`Invalid A2A ${field}.`); + this.name = 'InvalidA2AInputError'; + this.field = field; + } +} diff --git a/packages/a2a/src/handlers.ts b/packages/a2a/src/handlers.ts new file mode 100644 index 000000000..089a243f2 --- /dev/null +++ b/packages/a2a/src/handlers.ts @@ -0,0 +1,406 @@ +import type { Context, Env, Handler } from 'hono'; +import type { + A2AAgentCard, + A2AMessage, + A2AMessageHandlerInput, + A2AMessageHandlerResult, + A2ASendMessageResponse, + A2ATask, +} from './index.ts'; +import { + A2A_CONTENT_TYPE, + A2A_ERROR_REASONS, + AgentCard, + Message, + Role, + SendMessageConfiguration, + Task, +} from './types.ts'; + +const DEFAULT_BODY_LIMIT = 1024 * 1024; // 1 MiB +const JSON_CONTENT_TYPE = 'application/json'; + +interface AgentCardHandlerOptions { + agentCard: A2AAgentCard; +} + +export function createAgentCardHandler( + options: AgentCardHandlerOptions, +): Handler { + const cardJson = JSON.stringify(AgentCard.toJSON(options.agentCard)); + const headers = { + 'Content-Type': JSON_CONTENT_TYPE, + 'Cache-Control': 'public, max-age=3600', + } as const; + + return (c) => { + return c.newResponse(cardJson, { status: 200, headers }); + }; +} + +interface SendMessageHandlerOptions { + bodyLimit?: number; + onMessage(input: A2AMessageHandlerInput): A2AMessageHandlerResult; +} + +export function createSendMessageHandler( + options: SendMessageHandlerOptions, +): Handler { + const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT; + if (!Number.isSafeInteger(bodyLimit) || bodyLimit <= 0) { + throw new TypeError('A2A sendMessage bodyLimit must be a positive integer.'); + } + + return async (c) => { + const request = c.req.raw; + + // Validate content type — accept both application/a2a+json and application/json + const mediaType = request.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase(); + if (mediaType !== A2A_CONTENT_TYPE && mediaType !== JSON_CONTENT_TYPE) { + return new Response(null, { status: 415 }); + } + + // Check content length + const contentLength = request.headers.get('content-length'); + if (contentLength !== null) { + if (!/^\d+$/.test(contentLength)) return new Response(null, { status: 400 }); + if (Number(contentLength) > bodyLimit) return new Response(null, { status: 413 }); + } + + // Read and parse body + let body: Uint8Array | undefined; + try { + body = await readBody(request, bodyLimit); + } catch { + return new Response(null, { status: 400 }); + } + if (!body) return new Response(null, { status: 413 }); + + const parsed = parseJson(body); + if (!isRecord(parsed)) return a2aErrorResponse(400, 'Invalid JSON body.'); + + // Validate presence of required fields before SDK conversion + if (!parsed.message || !isRecord(parsed.message)) { + return a2aErrorResponse(400, 'Missing or invalid "message" field.'); + } + + const rawMessage = parsed.message as Record; + if (!rawMessage.messageId || typeof rawMessage.messageId !== 'string') { + return a2aErrorResponse(400, 'Missing or invalid "message.messageId".'); + } + if (!rawMessage.role || typeof rawMessage.role !== 'string') { + return a2aErrorResponse(400, 'Missing or invalid "message.role".'); + } + if (!Array.isArray(rawMessage.parts) || rawMessage.parts.length === 0) { + return a2aErrorResponse(400, 'Missing or empty "message.parts".'); + } + + // Convert to SDK types + const message: A2AMessage = Message.fromJSON(parsed.message); + const taskId = message.taskId || undefined; + const contextId = message.contextId || undefined; + const configuration = isRecord(parsed.configuration) + ? SendMessageConfiguration.fromJSON(parsed.configuration) + : undefined; + const metadata = isRecord(parsed.metadata) + ? (parsed.metadata as Record) + : undefined; + + // Terminal-state validation for taskId (spec Section 3.1.1: sending to + // a terminal task MUST return UnsupportedOperationError) is delegated + // to the onMessage callback, which owns task-state lookups. + + // Invoke the application callback + try { + const result = await options.onMessage({ + c: c as Context, + message, + taskId, + contextId, + configuration, + metadata, + }); + + return serializeResponse(result); + } catch (error) { + if (error instanceof A2AProtocolError) { + return a2aErrorResponse(error.statusCode, error.message, error.reason); + } + return a2aErrorResponse(500, 'Internal server error.'); + } + }; +} + +interface GetTaskHandlerOptions { + onGetTask(input: { + c: Context; + taskId: string; + historyLength?: number; + }): Promise | A2ATask | null; +} + +export function createGetTaskHandler( + options: GetTaskHandlerOptions, +): Handler { + return async (c) => { + const taskId = c.req.param('taskId'); + if (!taskId) return a2aErrorResponse(400, 'Missing task ID.'); + + const historyLengthParam = c.req.query('historyLength'); + const historyLength = + historyLengthParam !== undefined ? parseHistoryLength(historyLengthParam) : undefined; + + try { + const task = await options.onGetTask({ + c: c as Context, + taskId, + historyLength, + }); + if (!task) { + return a2aErrorResponse( + 404, + `Task ${taskId} not found.`, + A2A_ERROR_REASONS.TASK_NOT_FOUND, + ); + } + return Response.json(Task.toJSON(task), { + status: 200, + headers: { 'Content-Type': A2A_CONTENT_TYPE }, + }); + } catch { + return a2aErrorResponse(500, 'Internal server error.'); + } + }; +} + +interface CancelTaskHandlerOptions { + onCancelTask(input: { + c: Context; + taskId: string; + metadata?: Record; + }): Promise | A2ATask | null; +} + +/** + * The Hono route for cancel uses `/tasks/:taskIdAction` where the + * URL looks like `/tasks/{id}:cancel` (Google API custom-method + * convention). The handler parses the `:cancel` suffix from the + * captured parameter. + */ +export function createCancelTaskHandler( + options: CancelTaskHandlerOptions, +): Handler { + return async (c) => { + const raw = c.req.param('taskIdAction') ?? ''; + if (!raw.endsWith(':cancel')) { + return a2aErrorResponse(404, 'Not found.'); + } + const taskId = raw.slice(0, -':cancel'.length); + if (!taskId) return a2aErrorResponse(400, 'Missing task ID.'); + + let metadata: Record | undefined; + const mediaType = c.req.raw.headers + .get('content-type') + ?.split(';', 1)[0] + ?.trim() + .toLowerCase(); + if (mediaType === JSON_CONTENT_TYPE || mediaType === A2A_CONTENT_TYPE) { + try { + const body = await c.req.raw.text(); + if (body) { + const parsed = JSON.parse(body) as Record; + metadata = (parsed.metadata ?? undefined) as Record | undefined; + } + } catch { + // No body or invalid JSON is acceptable for cancel + } + } + + try { + // State validation (is the task cancelable?) is the responsibility + // of the onCancelTask callback. If the task cannot be canceled, the + // callback should throw A2AProtocolError with reason + // TASK_NOT_CANCELABLE and status 400. + const task = await options.onCancelTask({ + c: c as Context, + taskId, + metadata, + }); + if (!task) { + return a2aErrorResponse( + 404, + `Task ${taskId} not found.`, + A2A_ERROR_REASONS.TASK_NOT_FOUND, + ); + } + return Response.json(Task.toJSON(task), { + status: 200, + headers: { 'Content-Type': A2A_CONTENT_TYPE }, + }); + } catch (error) { + if (error instanceof A2AProtocolError) { + return a2aErrorResponse(error.statusCode, error.message, error.reason); + } + return a2aErrorResponse(500, 'Internal server error.'); + } + }; +} + +// --------------------------------------------------------------------------- +// A2AProtocolError — throwable from application callbacks +// --------------------------------------------------------------------------- + +export class A2AProtocolError extends Error { + /** A2A error reason code (e.g. `"TASK_NOT_FOUND"`). */ + readonly reason: string; + /** HTTP status code. */ + readonly statusCode: number; + + constructor(options: { status: number; reason: string; message?: string }) { + super(options.message ?? options.reason); + this.name = 'A2AProtocolError'; + this.reason = options.reason; + this.statusCode = options.status; + } +} + +/** Returns UnsupportedOperationError for unimplemented spec endpoints. */ +export function createUnsupportedHandler( + operation: string, +): Handler { + return () => { + return a2aErrorResponse( + 400, + `${operation} is not supported by this agent.`, + A2A_ERROR_REASONS.UNSUPPORTED_OPERATION, + ); + }; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function serializeResponse(value: A2ASendMessageResponse | undefined | Response): Response { + if (value === undefined) return new Response(null, { status: 200 }); + if (Object.prototype.toString.call(value) === '[object Response]') return value as Response; + + // Narrow to A2ASendMessageResponse after excluding undefined and Response + const result = value as A2ASendMessageResponse; + + // Serialize the Flue response envelope — convert SDK objects to wire format + const wireFormat: Record = {}; + if (result.task) wireFormat.task = Task.toJSON(result.task); + if (result.message) wireFormat.message = Message.toJSON(result.message); + + return Response.json(wireFormat, { + status: 200, + headers: { 'Content-Type': A2A_CONTENT_TYPE }, + }); +} + +/** Maps HTTP status codes to canonical google.rpc status names. */ +function httpStatusName(status: number): string { + switch (status) { + case 400: + return 'INVALID_ARGUMENT'; + case 401: + return 'UNAUTHENTICATED'; + case 403: + return 'PERMISSION_DENIED'; + case 404: + return 'NOT_FOUND'; + case 409: + return 'ABORTED'; + case 413: + return 'RESOURCE_EXHAUSTED'; + case 415: + return 'INVALID_ARGUMENT'; + case 500: + return 'INTERNAL'; + default: + return 'UNKNOWN'; + } +} + +/** + * Builds a `google.rpc.Status` JSON error response per A2A spec + * Section 11.6. + */ +function a2aErrorResponse( + httpStatus: number, + message: string, + reason?: string, +): Response { + const body = { + error: { + code: httpStatus, + status: httpStatusName(httpStatus), + message, + details: reason + ? [ + { + '@type': 'type.googleapis.com/google.rpc.ErrorInfo', + reason, + domain: 'a2a-protocol.org', + }, + ] + : [], + }, + }; + return Response.json(body, { + status: httpStatus, + headers: { 'Content-Type': A2A_CONTENT_TYPE }, + }); +} + +async function readBody(request: Request, bodyLimit: number): Promise { + if (!request.body) return new Uint8Array(); + const reader = request.body.getReader(); + const chunks: Uint8Array[] = []; + let total = 0; + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + total += value.byteLength; + if (total > bodyLimit) { + void reader.cancel(); + return undefined; + } + chunks.push(value); + } + } finally { + reader.releaseLock(); + } + const body = new Uint8Array(total); + let offset = 0; + for (const chunk of chunks) { + body.set(chunk, offset); + offset += chunk.byteLength; + } + return body; +} + +function parseJson(body: Uint8Array): unknown { + try { + return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(body)); + } catch { + return undefined; + } +} + +/** + * Parses a historyLength query parameter. The A2A spec (Section 3.2.4) + * allows negative values to mean "last N messages", so both positive + * and negative integers are accepted. + */ +function parseHistoryLength(value: string): number | undefined { + if (!/^-?\d+$/.test(value)) return undefined; + const parsed = Number(value); + return Number.isSafeInteger(parsed) ? parsed : undefined; +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} diff --git a/packages/a2a/src/index.ts b/packages/a2a/src/index.ts new file mode 100644 index 000000000..0499786ff --- /dev/null +++ b/packages/a2a/src/index.ts @@ -0,0 +1,417 @@ +import type { Context, Env, Handler } from 'hono'; +import { InvalidA2AConversationKeyError, InvalidA2AInputError } from './errors.ts'; +import { + A2AProtocolError, + createAgentCardHandler, + createCancelTaskHandler, + createGetTaskHandler, + createSendMessageHandler, + createUnsupportedHandler, +} from './handlers.ts'; +import { AgentCard } from './types.ts'; + +export { A2AProtocolError } from './handlers.ts'; +export { InvalidA2AConversationKeyError, InvalidA2AInputError } from './errors.ts'; +export { + A2A_CONTENT_TYPE, + A2A_ERROR_REASONS, + TERMINAL_TASK_STATES, + // SDK value re-exports (MessageFns with fromJSON/toJSON) + AgentCard, + AgentCapabilities, + AgentInterface, + AgentProvider, + AgentSkill, + Artifact, + Message, + Part, + Role, + SendMessageConfiguration, + SendMessageRequest, + SendMessageResponse, + Task, + TaskState, + roleFromJSON, + roleToJSON, + taskStateFromJSON, + taskStateToJSON, + // Type aliases (A2A-prefixed) + type A2AAgentCapabilities, + type A2AAgentCard, + type A2AAgentInterface, + type A2AAgentProvider, + type A2AAgentSkill, + type A2AArtifact, + type A2AMessage, + type A2APart, + type A2ARole, + type A2ARpcErrorDetail, + type A2ARpcStatus, + type A2ASendMessageConfiguration, + type A2ASendMessageRequest, + type A2ATask, + type A2ATaskState, + type A2ATaskStatus, +} from './types.ts'; + +export type JsonValue = + | null + | boolean + | number + | string + | JsonValue[] + | { [key: string]: JsonValue }; + +export interface ChannelRoute { + readonly method: string; + readonly path: string; + readonly handler: Handler; +} + +/** Canonical A2A task reference for conversation key mapping. */ +export interface A2ATaskRef { + taskId: string; +} + +/** Input provided to the onMessage callback. */ +export interface A2AMessageHandlerInput { + /** Hono context. */ + c: Context; + /** The A2A message from the client (SDK type). */ + message: import('./types.ts').A2AMessage; + /** Task ID if this message continues an existing task. */ + taskId?: string; + /** Context ID for grouping related interactions. */ + contextId?: string; + /** Request configuration (SDK type). */ + configuration?: import('./types.ts').A2ASendMessageConfiguration; + /** Additional metadata from the request. */ + metadata?: Record; +} + +/** + * Flue-specific response envelope for the onMessage callback. + * Contains either a task or a direct message response. + * The handler serializes SDK objects to wire format via `toJSON()`. + */ +export interface A2ASendMessageResponse { + /** Task created or updated by the message (SDK type). */ + task?: import('./types.ts').A2ATask; + /** Direct message response (SDK type). */ + message?: import('./types.ts').A2AMessage; +} + +type A2AMessageHandlerValue = + | undefined + | A2ASendMessageResponse + | Response; + +/** + * Returning nothing produces an empty `200`. A2A response objects become + * JSON responses, and Hono or Fetch responses pass through unchanged. + */ +export type A2AMessageHandlerResult = + | A2AMessageHandlerValue + | Promise; + +/** Simplified agent card configuration for common cases. */ +export interface A2AAgentCardConfig { + /** Agent name. */ + name: string; + /** Agent description. */ + description: string; + /** Agent version. */ + version: string; + /** Base URL where the agent is deployed. */ + url: string; + /** Agent skills (loose input — missing array fields default to empty). */ + skills: Array<{ + id: string; + name: string; + description: string; + tags: string[]; + examples?: string[]; + inputModes?: string[]; + outputModes?: string[]; + }>; + /** Agent provider. */ + provider?: { url: string; organization: string }; + /** Documentation URL. */ + documentationUrl?: string; + /** Icon URL. */ + iconUrl?: string; + /** Default input media types. Defaults to ["text/plain"]. */ + defaultInputModes?: string[]; + /** Default output media types. Defaults to ["text/plain"]. */ + defaultOutputModes?: string[]; +} + +/** Ingress configuration for an A2A endpoint. */ +export interface A2AChannelOptions { + /** + * Agent card configuration. Provide either a complete `A2AAgentCard` or + * a simplified `A2AAgentCardConfig` that will be expanded into a full card. + */ + agentCard: import('./types.ts').A2AAgentCard | A2AAgentCardConfig; + /** Maximum request-body size in bytes. Defaults to 1 MiB. */ + bodyLimit?: number; + /** + * Optional authentication callback. Called before every handler except + * the Agent Card discovery endpoint (`/.well-known/agent-card.json`). + * + * Return a `Response` to reject the request (e.g. 401/403), or + * `undefined`/`void` to allow it through. + * + * **WARNING:** This channel has **no built-in authentication**. + * Any internet-facing deployment MUST provide an `authenticate` + * callback to prevent unauthorized access. The A2A spec (Section 8) + * requires agents to declare and enforce security schemes. + */ + authenticate?(c: Context): Promise | Response | void; + /** Receives verified A2A messages. Required. */ + onMessage(input: A2AMessageHandlerInput): A2AMessageHandlerResult; + /** + * Retrieves a task by ID. Optional — if omitted, the `GET /tasks/:taskId` + * route is not registered. + */ + onGetTask?(input: { + c: Context; + taskId: string; + historyLength?: number; + }): Promise | import('./types.ts').A2ATask | null; + /** + * Cancels a task by ID. Optional — if omitted, the + * `POST /tasks/{id}:cancel` route returns `UnsupportedOperationError`. + * + * The callback is responsible for validating that the task is in a + * cancelable state. If the task cannot be canceled, throw + * `A2AProtocolError` with reason `TASK_NOT_CANCELABLE` and status 400. + */ + onCancelTask?(input: { + c: Context; + taskId: string; + metadata?: Record; + }): Promise | import('./types.ts').A2ATask | null; +} + +/** Verified ingress and canonical identity helpers. */ +export interface A2AChannel { + readonly routes: readonly ChannelRoute[]; + /** Serializes a canonical namespaced identifier. It is not an authorization capability. */ + conversationKey(ref: A2ATaskRef): string; + /** Parses only canonical keys produced by `conversationKey()`. */ + parseConversationKey(id: string): A2ATaskRef; +} + +/** + * Creates an A2A channel. + * + * Exposes A2A protocol endpoints as Hono route handlers following + * the Flue channel pattern. The Agent Card is served at + * `/.well-known/agent-card.json`. Messages are received via + * `POST /message:send` (colon-prefixed per the A2A HTTP+JSON + * binding, Section 11.3) and forwarded to the `onMessage` callback. + * + * The Agent Card endpoint is the only route that does **not** go + * through the optional `authenticate` callback — it is a public + * discovery endpoint per the spec. + */ +export function createA2AChannel( + options: A2AChannelOptions, +): A2AChannel { + validateOptions(options); + + const agentCard = normalizeAgentCard(options.agentCard); + const routes: ChannelRoute[] = []; + const auth = options.authenticate; + + // Agent Card discovery endpoint (public — no auth) + routes.push({ + method: 'GET', + path: '/.well-known/agent-card.json', + handler: createAgentCardHandler({ agentCard }), + }); + + // SendMessage endpoint — colon-prefixed verb per A2A spec + routes.push({ + method: 'POST', + path: '/message:send', + handler: withAuth( + auth, + createSendMessageHandler({ + bodyLimit: options.bodyLimit, + onMessage: options.onMessage, + }), + ), + }); + + // SendStreamingMessage stub — spec requires UnsupportedOperationError + // instead of a plain 404 when streaming is not supported (Section 3.1.2). + routes.push({ + method: 'POST', + path: '/message:stream', + handler: withAuth(auth, createUnsupportedHandler('SendStreamingMessage')), + }); + + // GetTask endpoint (optional) + if (options.onGetTask) { + routes.push({ + method: 'GET', + path: '/tasks/:taskId', + handler: withAuth(auth, createGetTaskHandler({ onGetTask: options.onGetTask })), + }); + } + + // ListTasks stub — spec Section 3.1.4 lists this as a core operation. + // Return UnsupportedOperationError until a full implementation is added. + routes.push({ + method: 'GET', + path: '/tasks', + handler: withAuth(auth, createUnsupportedHandler('ListTasks')), + }); + + // CancelTask endpoint — uses `/tasks/:taskIdAction` pattern because + // the A2A spec URL is `/tasks/{id}:cancel` (Google API custom-method + // convention). The handler parses the `:cancel` suffix. + if (options.onCancelTask) { + routes.push({ + method: 'POST', + path: '/tasks/:taskIdAction', + handler: withAuth(auth, createCancelTaskHandler({ onCancelTask: options.onCancelTask })), + }); + } else { + routes.push({ + method: 'POST', + path: '/tasks/:taskIdAction', + handler: withAuth(auth, createUnsupportedHandler('CancelTask')), + }); + } + + const channel: A2AChannel = { + routes, + conversationKey(ref) { + assertTaskRef(ref); + return `a2a:v1:${encodeURIComponent(ref.taskId)}`; + }, + parseConversationKey(id) { + try { + const match = /^a2a:v1:([^:]+)$/.exec(id); + const taskId = match?.[1]; + if (!taskId) throw new InvalidA2AConversationKeyError(); + const ref = { taskId: decodeURIComponent(taskId) }; + assertTaskRef(ref); + if (channel.conversationKey(ref) !== id) throw new InvalidA2AConversationKeyError(); + return ref; + } catch (error) { + if (error instanceof InvalidA2AConversationKeyError) throw error; + throw new InvalidA2AConversationKeyError(); + } + }, + }; + + return channel; +} + +// --------------------------------------------------------------------------- +// Agent Card normalization +// --------------------------------------------------------------------------- + +function isFullAgentCard( + card: import('./types.ts').A2AAgentCard | A2AAgentCardConfig, +): card is import('./types.ts').A2AAgentCard { + return 'supportedInterfaces' in card; +} + +function normalizeAgentCard( + input: import('./types.ts').A2AAgentCard | A2AAgentCardConfig, +): import('./types.ts').A2AAgentCard { + if (isFullAgentCard(input)) return input; + + // Use AgentCard.fromJSON to properly construct an SDK-typed agent card + // with all default values filled in. + return AgentCard.fromJSON({ + name: input.name, + description: input.description, + version: input.version, + supportedInterfaces: [ + { + url: input.url, + protocolBinding: 'HTTP+JSON', + protocolVersion: '1.0', + }, + ], + provider: input.provider, + documentationUrl: input.documentationUrl, + capabilities: { + streaming: false, + pushNotifications: false, + }, + defaultInputModes: input.defaultInputModes ?? ['text/plain'], + defaultOutputModes: input.defaultOutputModes ?? ['text/plain'], + skills: input.skills, + iconUrl: input.iconUrl, + }); +} + +// --------------------------------------------------------------------------- +// Validation +// --------------------------------------------------------------------------- + +function validateOptions(options: A2AChannelOptions): void { + if (!options || typeof options !== 'object') { + throw new TypeError('createA2AChannel() requires an options object.'); + } + if (!options.agentCard || typeof options.agentCard !== 'object') { + throw new TypeError('createA2AChannel() requires an agentCard.'); + } + if (typeof options.onMessage !== 'function') { + throw new TypeError('createA2AChannel() requires an onMessage handler.'); + } + + const card = options.agentCard; + if (typeof card.name !== 'string' || card.name.length === 0) { + throw new TypeError('Agent card name must be a non-empty string.'); + } + if (typeof card.description !== 'string' || card.description.length === 0) { + throw new TypeError('Agent card description must be a non-empty string.'); + } + if (typeof card.version !== 'string' || card.version.length === 0) { + throw new TypeError('Agent card version must be a non-empty string.'); + } + if (!isFullAgentCard(card)) { + if (typeof card.url !== 'string' || card.url.length === 0) { + throw new TypeError('Agent card url must be a non-empty string.'); + } + } + const skills = card.skills; + if (!Array.isArray(skills) || skills.length === 0) { + throw new TypeError('Agent card must have at least one skill.'); + } +} + +function assertTaskRef(ref: A2ATaskRef): void { + if (!ref || typeof ref !== 'object') throw new InvalidA2AInputError('ref'); + assertIdentifier(ref.taskId, 'taskId'); +} + +function assertIdentifier(value: unknown, field: string): asserts value is string { + if (typeof value !== 'string' || value.length === 0 || value.trim() !== value) { + throw new InvalidA2AInputError(field); + } +} + +// --------------------------------------------------------------------------- +// Auth wrapper +// --------------------------------------------------------------------------- + +function withAuth( + authenticate: ((c: Context) => Promise | Response | void) | undefined, + handler: Handler, +): Handler { + if (!authenticate) return handler; + return async (c, next) => { + const rejection = await authenticate(c as Context); + if (rejection) return rejection; + return handler(c, next); + }; +} diff --git a/packages/a2a/src/types.ts b/packages/a2a/src/types.ts new file mode 100644 index 000000000..b63547773 --- /dev/null +++ b/packages/a2a/src/types.ts @@ -0,0 +1,107 @@ +/** + * A2A protocol types — re-exported from the official @a2a-js/sdk. + * Flue-specific constants and error types are defined locally. + * + * @see https://a2a-protocol.org/v1.0.0/specification/ + */ + +import { TaskState } from '@a2a-js/sdk'; + +// --------------------------------------------------------------------------- +// Type re-exports (A2A-prefixed aliases for backward compatibility) +// --------------------------------------------------------------------------- + +export type { Part as A2APart } from '@a2a-js/sdk'; +export type { Role as A2ARole } from '@a2a-js/sdk'; +export type { Message as A2AMessage } from '@a2a-js/sdk'; +export type { Artifact as A2AArtifact } from '@a2a-js/sdk'; +export type { TaskState as A2ATaskState } from '@a2a-js/sdk'; +export type { TaskStatus as A2ATaskStatus } from '@a2a-js/sdk'; +export type { Task as A2ATask } from '@a2a-js/sdk'; +export type { SendMessageConfiguration as A2ASendMessageConfiguration } from '@a2a-js/sdk'; +export type { SendMessageRequest as A2ASendMessageRequest } from '@a2a-js/sdk'; +export type { AgentInterface as A2AAgentInterface } from '@a2a-js/sdk'; +export type { AgentCapabilities as A2AAgentCapabilities } from '@a2a-js/sdk'; +export type { AgentSkill as A2AAgentSkill } from '@a2a-js/sdk'; +export type { AgentProvider as A2AAgentProvider } from '@a2a-js/sdk'; +export type { AgentCard as A2AAgentCard } from '@a2a-js/sdk'; + +// --------------------------------------------------------------------------- +// Value re-exports (enums, MessageFns, converters, constants) +// --------------------------------------------------------------------------- + +export { + TaskState, + Role, + Message, + Task, + Part, + Artifact, + SendMessageConfiguration, + SendMessageRequest, + SendMessageResponse, + AgentCard, + AgentInterface, + AgentCapabilities, + AgentSkill, + AgentProvider, + taskStateFromJSON, + taskStateToJSON, + roleFromJSON, + roleToJSON, + A2A_CONTENT_TYPE, +} from '@a2a-js/sdk'; + +// --------------------------------------------------------------------------- +// Flue-specific constants +// --------------------------------------------------------------------------- + +/** Terminal states that cannot accept further messages. */ +export const TERMINAL_TASK_STATES: ReadonlySet = new Set([ + TaskState.TASK_STATE_COMPLETED, + TaskState.TASK_STATE_FAILED, + TaskState.TASK_STATE_CANCELED, + TaskState.TASK_STATE_REJECTED, +]); + +/** + * A2A error reason codes per Section 11.6. + * + * These are used as `reason` values inside `google.rpc.ErrorInfo` + * details — not as URIs. + */ +export const A2A_ERROR_REASONS = { + TASK_NOT_FOUND: 'TASK_NOT_FOUND', + TASK_NOT_CANCELABLE: 'TASK_NOT_CANCELABLE', + UNSUPPORTED_OPERATION: 'UNSUPPORTED_OPERATION', + CONTENT_TYPE_NOT_SUPPORTED: 'CONTENT_TYPE_NOT_SUPPORTED', + VERSION_NOT_SUPPORTED: 'VERSION_NOT_SUPPORTED', +} as const; + +// --------------------------------------------------------------------------- +// Error response types (A2A HTTP+JSON binding, Section 11.6) +// --------------------------------------------------------------------------- + +/** google.rpc.ErrorInfo detail entry used in A2A error responses. */ +export interface A2ARpcErrorDetail { + '@type': string; + reason: string; + domain: string; +} + +/** + * Error response envelope using the `google.rpc.Status` JSON + * representation required by the A2A HTTP+JSON binding (Section 11.6). + */ +export interface A2ARpcStatus { + error: { + /** HTTP status code. */ + code: number; + /** Canonical gRPC/Google-API status name (e.g. `"NOT_FOUND"`). */ + status: string; + /** Human-readable error message. */ + message: string; + /** Structured error details. */ + details: A2ARpcErrorDetail[]; + }; +} diff --git a/packages/a2a/tsconfig.json b/packages/a2a/tsconfig.json new file mode 100644 index 000000000..1a82dd560 --- /dev/null +++ b/packages/a2a/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "lib": ["ESNext", "DOM"] }, + "include": ["src"] +} diff --git a/packages/a2a/tsdown.config.ts b/packages/a2a/tsdown.config.ts new file mode 100644 index 000000000..5f1a6b25e --- /dev/null +++ b/packages/a2a/tsdown.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + dts: true, + clean: true, +});