From 0c0b4237ca6054d0462b78f92709a2800450588f Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Thu, 16 Apr 2026 10:12:07 -0500 Subject: [PATCH 01/17] feat(typescript): add TypeScript samples for the Agent Payments Protocol Mirrors the structure of samples/python and samples/go: src/{roles,common} laid out by role, scenarios/a2a/human-present/cards with README + run.sh, agent.ts + tools.ts split per role, Apache 2.0 headers, .env.example, ESLint flat config, and a vitest e2e smoke test. Built on Google ADK 0.6 (@google/adk, @google/adk-devtools), the A2A SDK (@a2a-js/sdk), gemini-2.5-flash, and W3C Verifiable Credentials via @digitalbazaar/vc. --- .cspell/custom-words.txt | 3 + .github/linters/.eslintrc.json | 39 + .github/workflows/linter.yaml | 9 + samples/typescript/.env.example | 10 + samples/typescript/.gitignore | 27 + samples/typescript/README.md | 74 + samples/typescript/eslint.config.js | 50 + samples/typescript/package-lock.json | 14689 ++++++++++++++++ samples/typescript/package.json | 73 + .../a2a/human-present/cards/README.md | 125 + .../scenarios/a2a/human-present/cards/run.sh | 51 + .../typescript/src/common/config/session.ts | 28 + .../typescript/src/common/constants/index.ts | 48 + .../src/common/schemas/cart-mandate.ts | 192 + .../src/common/schemas/intent-mandate.ts | 26 + .../src/common/schemas/payment-mandate.ts | 93 + .../src/common/schemas/payment-receipt.ts | 74 + .../src/common/schemas/shipping-address.ts | 30 + .../src/common/server/a2a-context.ts | 93 + .../src/common/server/base-executor.ts | 464 + .../typescript/src/common/server/bootstrap.ts | 96 + .../src/common/server/middleware.ts | 121 + .../src/common/types/cart-mandate.ts | 20 + .../src/common/types/intent-mandate.ts | 20 + .../src/common/types/payment-item.ts | 27 + .../src/common/types/payment-mandate.ts | 20 + .../src/common/types/payment-receipt.ts | 20 + .../typescript/src/common/utils/artifact.ts | 70 + .../typescript/src/common/utils/message.ts | 83 + .../typescript/src/common/vc/ap2-context.ts | 50 + .../typescript/src/common/vc/credentials.ts | 183 + .../src/common/vc/document-loader.ts | 118 + samples/typescript/src/common/vc/index.ts | 65 + .../typescript/src/common/vc/key-manager.ts | 127 + samples/typescript/src/common/vc/types.ts | 113 + .../credentials-provider/account-manager.ts | 324 + .../src/roles/credentials-provider/agent.ts | 57 + .../src/roles/credentials-provider/server.ts | 144 + .../src/roles/credentials-provider/tools.ts | 360 + samples/typescript/src/roles/index.ts | 22 + .../typescript/src/roles/merchant/agent.ts | 50 + .../typescript/src/roles/merchant/server.ts | 190 + .../typescript/src/roles/merchant/storage.ts | 57 + .../typescript/src/roles/merchant/tools.ts | 542 + .../src/roles/payment-processor/agent.ts | 41 + .../src/roles/payment-processor/server.ts | 91 + .../src/roles/payment-processor/tools.ts | 396 + .../typescript/src/roles/shopping/agent.ts | 101 + .../typescript/src/roles/shopping/server.ts | 127 + .../subagents/payment-collector/agent.ts | 66 + .../subagents/payment-collector/tools.ts | 227 + .../subagents/shipping-collector/agent.ts | 73 + .../subagents/shipping-collector/tools.ts | 134 + .../roles/shopping/subagents/shopper/agent.ts | 96 + .../roles/shopping/subagents/shopper/tools.ts | 239 + .../typescript/src/roles/shopping/tools.ts | 487 + .../typescript/test/e2e/connectivity.test.ts | 51 + .../typescript/test/e2e/payment-flow.test.ts | 53 + samples/typescript/tsconfig.json | 15 + samples/typescript/vitest.config.ts | 23 + 60 files changed, 21297 insertions(+) create mode 100644 .github/linters/.eslintrc.json create mode 100644 samples/typescript/.env.example create mode 100644 samples/typescript/.gitignore create mode 100644 samples/typescript/README.md create mode 100644 samples/typescript/eslint.config.js create mode 100644 samples/typescript/package-lock.json create mode 100644 samples/typescript/package.json create mode 100644 samples/typescript/scenarios/a2a/human-present/cards/README.md create mode 100755 samples/typescript/scenarios/a2a/human-present/cards/run.sh create mode 100644 samples/typescript/src/common/config/session.ts create mode 100644 samples/typescript/src/common/constants/index.ts create mode 100644 samples/typescript/src/common/schemas/cart-mandate.ts create mode 100644 samples/typescript/src/common/schemas/intent-mandate.ts create mode 100644 samples/typescript/src/common/schemas/payment-mandate.ts create mode 100644 samples/typescript/src/common/schemas/payment-receipt.ts create mode 100644 samples/typescript/src/common/schemas/shipping-address.ts create mode 100644 samples/typescript/src/common/server/a2a-context.ts create mode 100644 samples/typescript/src/common/server/base-executor.ts create mode 100644 samples/typescript/src/common/server/bootstrap.ts create mode 100644 samples/typescript/src/common/server/middleware.ts create mode 100644 samples/typescript/src/common/types/cart-mandate.ts create mode 100644 samples/typescript/src/common/types/intent-mandate.ts create mode 100644 samples/typescript/src/common/types/payment-item.ts create mode 100644 samples/typescript/src/common/types/payment-mandate.ts create mode 100644 samples/typescript/src/common/types/payment-receipt.ts create mode 100644 samples/typescript/src/common/utils/artifact.ts create mode 100644 samples/typescript/src/common/utils/message.ts create mode 100644 samples/typescript/src/common/vc/ap2-context.ts create mode 100644 samples/typescript/src/common/vc/credentials.ts create mode 100644 samples/typescript/src/common/vc/document-loader.ts create mode 100644 samples/typescript/src/common/vc/index.ts create mode 100644 samples/typescript/src/common/vc/key-manager.ts create mode 100644 samples/typescript/src/common/vc/types.ts create mode 100644 samples/typescript/src/roles/credentials-provider/account-manager.ts create mode 100644 samples/typescript/src/roles/credentials-provider/agent.ts create mode 100644 samples/typescript/src/roles/credentials-provider/server.ts create mode 100644 samples/typescript/src/roles/credentials-provider/tools.ts create mode 100644 samples/typescript/src/roles/index.ts create mode 100644 samples/typescript/src/roles/merchant/agent.ts create mode 100644 samples/typescript/src/roles/merchant/server.ts create mode 100644 samples/typescript/src/roles/merchant/storage.ts create mode 100644 samples/typescript/src/roles/merchant/tools.ts create mode 100644 samples/typescript/src/roles/payment-processor/agent.ts create mode 100644 samples/typescript/src/roles/payment-processor/server.ts create mode 100644 samples/typescript/src/roles/payment-processor/tools.ts create mode 100644 samples/typescript/src/roles/shopping/agent.ts create mode 100644 samples/typescript/src/roles/shopping/server.ts create mode 100644 samples/typescript/src/roles/shopping/subagents/payment-collector/agent.ts create mode 100644 samples/typescript/src/roles/shopping/subagents/payment-collector/tools.ts create mode 100644 samples/typescript/src/roles/shopping/subagents/shipping-collector/agent.ts create mode 100644 samples/typescript/src/roles/shopping/subagents/shipping-collector/tools.ts create mode 100644 samples/typescript/src/roles/shopping/subagents/shopper/agent.ts create mode 100644 samples/typescript/src/roles/shopping/subagents/shopper/tools.ts create mode 100644 samples/typescript/src/roles/shopping/tools.ts create mode 100644 samples/typescript/test/e2e/connectivity.test.ts create mode 100644 samples/typescript/test/e2e/payment-flow.test.ts create mode 100644 samples/typescript/tsconfig.json create mode 100644 samples/typescript/vitest.config.ts diff --git a/.cspell/custom-words.txt b/.cspell/custom-words.txt index ce73c361..84b1dd94 100644 --- a/.cspell/custom-words.txt +++ b/.cspell/custom-words.txt @@ -57,9 +57,11 @@ Garena gemini genai generativeai +genkit genproto glog gofmt +googleai gopkg gradletasknamecache gradlew @@ -169,6 +171,7 @@ Truelayer Trulioo udpa unmarshal +uuidv viewmodel vulnz Wallex diff --git a/.github/linters/.eslintrc.json b/.github/linters/.eslintrc.json new file mode 100644 index 00000000..07583b4a --- /dev/null +++ b/.github/linters/.eslintrc.json @@ -0,0 +1,39 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "env": { + "node": true, + "es2022": true + }, + "rules": { + "@typescript-eslint/no-explicit-any": "error" + }, + "overrides": [ + { + "files": ["samples/**/*.ts"], + "parserOptions": { + "project": "./samples/typescript/tsconfig.json" + }, + "settings": { + "import/resolver": { + "typescript": { + "alwaysTryTypes": true, + "project": "./samples/typescript/tsconfig.json" + } + } + }, + "rules": { + "@typescript-eslint/no-explicit-any": "off" + } + } + ] +} diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml index fea1b9c1..85164dbe 100644 --- a/.github/workflows/linter.yaml +++ b/.github/workflows/linter.yaml @@ -15,6 +15,15 @@ jobs: with: fetch-depth: 0 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install TypeScript Sample Dependencies + working-directory: samples/typescript + run: npm ci + - name: Lint Code Base uses: super-linter/super-linter/slim@v8 env: diff --git a/samples/typescript/.env.example b/samples/typescript/.env.example new file mode 100644 index 00000000..aaf46d93 --- /dev/null +++ b/samples/typescript/.env.example @@ -0,0 +1,10 @@ +# Copy this file to .env and fill in your own values. + +# --- Google AI Studio (Option 1, recommended for development) --- +# Obtain a key from https://aistudio.google.com/apikey +GOOGLE_API_KEY=your_google_api_key_here + +# --- Vertex AI (Option 2, recommended for production) --- +# GOOGLE_GENAI_USE_VERTEXAI=true +# GOOGLE_CLOUD_PROJECT=your-project-id +# GOOGLE_CLOUD_LOCATION=global diff --git a/samples/typescript/.gitignore b/samples/typescript/.gitignore new file mode 100644 index 00000000..46a9e221 --- /dev/null +++ b/samples/typescript/.gitignore @@ -0,0 +1,27 @@ +# Dependencies +node_modules/ + +# Build output +dist/ + +# Environment +.env +.env.* +!.env.example + +# Source maps +*.js.map + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Test coverage +coverage/ + +# Debug logs +*.log diff --git a/samples/typescript/README.md b/samples/typescript/README.md new file mode 100644 index 00000000..a34f10bd --- /dev/null +++ b/samples/typescript/README.md @@ -0,0 +1,74 @@ +# TypeScript Samples for the Agent Payments Protocol (AP2) + +This directory contains TypeScript samples demonstrating how to build AP2 +agents using the [Agent Development Kit (ADK)](https://google.github.io/adk-docs/) +and the [A2A SDK](https://www.npmjs.com/package/@a2a-js/sdk). + +## Available Scenarios + +- **[Human-Present Card Payment](./scenarios/a2a/human-present/cards/README.md)** + - Complete card payment flow with all four agents implemented in TypeScript. + +See the [scenario README](./scenarios/a2a/human-present/cards/README.md) for +detailed setup and usage instructions. + +## Why TypeScript for AP2 Agents? + +TypeScript is a natural fit for AP2 agents that are deployed alongside web, +Node.js, or edge runtimes: + +- **Type Safety**: Compile-time validation of protocol structures via Zod + schemas mirrored from the AP2 reference types. +- **Ecosystem**: Direct access to the npm ecosystem and the official + `@a2a-js/sdk` and `@google/adk` packages. +- **Portability**: Run on any Node.js 18+ runtime, including serverless and + container platforms. + +## Project Structure + +```text +samples/typescript/ +├── src/ +│ ├── roles/ # Agent role implementations and entry points +│ │ ├── shopping/ # Shopping Agent (root orchestrator) +│ │ │ └── subagents/ # shopper, shipping-collector, payment-collector +│ │ ├── merchant/ # Merchant Agent +│ │ ├── credentials-provider/ +│ │ └── payment-processor/ +│ └── common/ # Shared modules used across roles +│ ├── server/ # A2A server bootstrap, executor, middleware +│ ├── utils/ # Message and artifact helpers +│ ├── types/ # AP2 protocol object types +│ ├── schemas/ # Zod schemas mirroring the protocol +│ ├── vc/ # W3C Verifiable Credential helpers +│ ├── config/ # Session and runtime configuration +│ └── constants/ # Shared constants +├── test/ +│ └── e2e/ # End-to-end smoke tests +└── scenarios/ + └── a2a/ + └── human-present/ + └── cards/ # Card payment scenario +``` + +## Development + +```sh +# Install dependencies +npm install + +# Type-check and build +npm run build + +# Start all four agents plus the ADK web UI +npm run dev + +# Run the e2e smoke tests against running agents +npm test +``` + +See the scenario README for a full walkthrough. + +## License + +Copyright 2025 Google LLC. Licensed under the Apache License, Version 2.0. diff --git a/samples/typescript/eslint.config.js b/samples/typescript/eslint.config.js new file mode 100644 index 00000000..8188c131 --- /dev/null +++ b/samples/typescript/eslint.config.js @@ -0,0 +1,50 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import importPlugin from 'eslint-plugin-import'; +import globals from 'globals'; + +export default [ + { ignores: ['dist/**', 'node_modules/**', 'coverage/**'] }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['src/**/*.ts', 'test/**/*.ts'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + globals: { ...globals.node }, + parserOptions: { + project: './tsconfig.json', + }, + }, + plugins: { import: importPlugin }, + rules: { + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + 'import/no-unresolved': 'off', + }, + }, +]; diff --git a/samples/typescript/package-lock.json b/samples/typescript/package-lock.json new file mode 100644 index 00000000..d8b05190 --- /dev/null +++ b/samples/typescript/package-lock.json @@ -0,0 +1,14689 @@ +{ + "name": "ap2-typescript-samples", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ap2-typescript-samples", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@a2a-js/sdk": "^0.3.13", + "@digitalbazaar/ed25519-signature-2020": "^5.4.0", + "@digitalbazaar/ed25519-verification-key-2020": "^4.2.0", + "@digitalbazaar/vc": "^7.3.0", + "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", + "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", + "@google-cloud/storage": "^7.19.0", + "@google/adk": "^0.6.1", + "@google/genai": "^1.37.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.205.0", + "@opentelemetry/core": "^2.1.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.205.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.205.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.205.0", + "@opentelemetry/resource-detector-gcp": "^0.40.0", + "@opentelemetry/resources": "^2.1.0", + "@opentelemetry/sdk-logs": "^0.205.0", + "@opentelemetry/sdk-metrics": "^2.1.0", + "@opentelemetry/sdk-node": "^0.205.0", + "@opentelemetry/sdk-trace-base": "^2.1.0", + "@opentelemetry/sdk-trace-node": "^2.1.0", + "dotenv": "^17.4.2", + "express": "^5.1.0", + "uuid": "^13.0.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@google/adk-devtools": "^0.6.1", + "@types/express": "^5.0.6", + "@types/node": "^25.0.10", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^8.53.1", + "@typescript-eslint/parser": "^8.53.1", + "concurrently": "^9.1.2", + "eslint": "^9.39.4", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.32.0", + "globals": "^17.5.0", + "tsx": "^4.21.0", + "typescript": "^5.7.2", + "typescript-eslint": "^8.58.2", + "vitest": "^4.1.4" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@a2a-js/sdk": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-0.3.13.tgz", + "integrity": "sha512-BZr0f9JVNQs3GKOM9xINWCh6OKIJWZFPyqqVqTym5mxO2Eemc6I/0zL7zWnljHzGdaf5aZQyQN5xa6PSH62q+A==", + "license": "Apache-2.0", + "dependencies": { + "uuid": "^11.1.0" + }, + "engines": { + "node": ">=18" + }, + "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/@a2a-js/sdk/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@azure-rest/core-client": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.6.0.tgz", + "integrity": "sha512-iuFKDm8XPzNxPfRjhyU5/xKZmcRDzSuEghXDHHk4MjBV/wFL34GmYVBZnn9wmuoLBeS1qAw9ceMdaeJBPcB1QQ==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-http-compat": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.4.0.tgz", + "integrity": "sha512-f1P96IB399YiN2ARYHP7EpZi3Bf3wH4SN2lGzrw7JVwm7bbsVYtf2iKSBwTywD2P62NOPZGHFSZi+6jjb75JuA==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@azure/core-client": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", + "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz", + "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz", + "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/keyvault-common": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.1.0.tgz", + "integrity": "sha512-aCDidWuKY06LWQ4x7/8TIXK6iRqTaRWRL3t7T+LC+j1b07HtoIsOxP/tU90G4jCSBn5TAyUTCtA4MS/y5Hudaw==", + "license": "MIT", + "dependencies": { + "@azure-rest/core-client": "^2.3.3", + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-rest-pipeline": "^1.8.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.10.0", + "@azure/logger": "^1.1.4", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/keyvault-keys": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.10.0.tgz", + "integrity": "sha512-eDT7iXoBTRZ2n3fLiftuGJFD+yjkiB1GNqzU2KbY1TLYeXeSPVTVgn2eJ5vmRTZ11978jy2Kg2wI7xa9Tyr8ag==", + "license": "MIT", + "dependencies": { + "@azure-rest/core-client": "^2.3.3", + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.9.0", + "@azure/core-http-compat": "^2.2.0", + "@azure/core-lro": "^2.7.2", + "@azure/core-paging": "^1.6.2", + "@azure/core-rest-pipeline": "^1.19.0", + "@azure/core-tracing": "^1.2.0", + "@azure/core-util": "^1.11.0", + "@azure/keyvault-common": "^2.0.0", + "@azure/logger": "^1.1.4", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.6.3.tgz", + "integrity": "sha512-sTjMtUm+bJpENU/1WlRzHEsgEHppZDZ1EtNyaOODg/sQBtMxxJzGB+MOCM+T2Q5Qe1fKBrdxUmjyRxm0r7Ez9w==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.4.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.4.1.tgz", + "integrity": "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.1.2.tgz", + "integrity": "sha512-DoeSJ9U5KPAIZoHsPywvfEj2MhBniQe0+FSpjLUTdWoIkI999GB5USkW6nNEHnIaLVxROHXvprWA1KzdS1VQ4A==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.4.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@azure/msal-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@clack/core": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz", + "integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz", + "integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "0.5.0", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@digitalbazaar/credentials-context": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@digitalbazaar/credentials-context/-/credentials-context-3.2.0.tgz", + "integrity": "sha512-WruXZAF182pCYq/z2JPJ4N4mVDu6pd/hr7widdCqbKekA53BXHoX7XhL9tmRmQ8kfmkaStBfla67QHhkchWYBQ==", + "license": "SEE LICENSE IN LICENSE.md" + }, + "node_modules/@digitalbazaar/ed25519-multikey": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@digitalbazaar/ed25519-multikey/-/ed25519-multikey-1.3.1.tgz", + "integrity": "sha512-55qIbOaAyswVCFfZ70ap7SN2bDSwYmcVbUtGCrpVF/CjuJ8IPqf6z8fDPJzB+CE7Q896SaZlcDukz4LOwIRCPA==", + "license": "BSD-3-Clause", + "dependencies": { + "@noble/ed25519": "^1.6.0", + "base58-universal": "^2.0.0", + "base64url-universal": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@digitalbazaar/ed25519-signature-2020": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@digitalbazaar/ed25519-signature-2020/-/ed25519-signature-2020-5.4.0.tgz", + "integrity": "sha512-dHjOv41wiKLoPaE1S9g4NCMKNnelYtBa5fCJKwrqo+cPlLuKcEhtD7w/uuc5non7bf/GX5sa5YfDI/sRUOS6iw==", + "license": "BSD-3-Clause", + "dependencies": { + "@digitalbazaar/ed25519-multikey": "^1.1.0", + "@digitalbazaar/ed25519-verification-key-2020": "^4.1.0", + "base58-universal": "^2.0.0", + "ed25519-signature-2020-context": "^1.1.0", + "jsonld-signatures": "^11.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@digitalbazaar/ed25519-verification-key-2020": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@digitalbazaar/ed25519-verification-key-2020/-/ed25519-verification-key-2020-4.2.0.tgz", + "integrity": "sha512-urEVTYkt+uYD8GjdoS6gkm2sKBich1hqx42b6vvUOmNgF0agZ95JlUjiJXEx+VOwu7WSjJSnEBph2Qatkrk1CA==", + "license": "BSD-3-Clause", + "dependencies": { + "@noble/ed25519": "^1.6.0", + "base58-universal": "^2.0.0", + "base64url-universal": "^2.0.0", + "crypto-ld": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@digitalbazaar/http-client": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-4.3.0.tgz", + "integrity": "sha512-6lMpxpt9BOmqHKGs9Xm6DP4LlZTBFer/ZjHvP3FcW3IaUWYIWC7dw5RFZnvw4fP57kAVcm1dp3IF+Y50qhBvAw==", + "license": "BSD-3-Clause", + "dependencies": { + "ky": "^1.14.2", + "undici": "^6.23.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@digitalbazaar/security-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@digitalbazaar/security-context/-/security-context-1.0.1.tgz", + "integrity": "sha512-0WZa6tPiTZZF8leBtQgYAfXQePFQp2z5ivpCEN/iZguYYZ0TB9qRmWtan5XH6mNFuusHtMcyIzAcReyE6rZPhA==", + "license": "BSD-3-Clause" + }, + "node_modules/@digitalbazaar/vc": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@digitalbazaar/vc/-/vc-7.3.0.tgz", + "integrity": "sha512-liPd4fCQYGXB3aTUY7XNt2A41EGwwdCJdqQvOjXp1ySwLptvifOYA/EvlViJfFgjDeU5DaSEeB+2V1/BY6oMHA==", + "license": "BSD-3-Clause", + "dependencies": { + "@digitalbazaar/credentials-context": "^3.2.0", + "jsonld": "^9.0.0", + "jsonld-signatures": "^11.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, + "node_modules/@google-cloud/opentelemetry-cloud-monitoring-exporter": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@google-cloud/opentelemetry-cloud-monitoring-exporter/-/opentelemetry-cloud-monitoring-exporter-0.21.0.tgz", + "integrity": "sha512-+lAew44pWt6rA4l8dQ1gGhH7Uo95wZKfq/GBf9aEyuNDDLQ2XppGEEReu6ujesSqTtZ8ueQFt73+7SReSHbwqg==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/opentelemetry-resource-util": "^3.0.0", + "@google-cloud/precise-date": "^4.0.0", + "google-auth-library": "^9.0.0", + "googleapis": "^137.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-metrics": "^2.0.0" + } + }, + "node_modules/@google-cloud/opentelemetry-cloud-trace-exporter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/opentelemetry-cloud-trace-exporter/-/opentelemetry-cloud-trace-exporter-3.0.0.tgz", + "integrity": "sha512-mUfLJBFo+ESbO0dAGboErx2VyZ7rbrHcQvTP99yH/J72dGaPbH2IzS+04TFbTbEd1VW5R9uK3xq2CqawQaG+1Q==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/opentelemetry-resource-util": "^3.0.0", + "@grpc/grpc-js": "^1.1.8", + "@grpc/proto-loader": "^0.8.0", + "google-auth-library": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-trace-base": "^2.0.0" + } + }, + "node_modules/@google-cloud/opentelemetry-resource-util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/opentelemetry-resource-util/-/opentelemetry-resource-util-3.0.0.tgz", + "integrity": "sha512-CGR/lNzIfTKlZoZFfS6CkVzx+nsC9gzy6S8VcyaLegfEJbiPjxbMLP7csyhJTvZe/iRRcQJxSk0q8gfrGqD3/Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.22.0", + "gcp-metadata": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/precise-date": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/precise-date/-/precise-date-4.0.0.tgz", + "integrity": "sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz", + "integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "<4.1.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^5.3.4", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@google/adk": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@google/adk/-/adk-0.6.1.tgz", + "integrity": "sha512-AHWWM5pEOaZCZq4MASFbnnm5v6L71rt5LRt4qcnyJBjltCTgLevEj7VDGSYNe3FExsSVFyMmc9/1FYpbkjYzAw==", + "license": "Apache-2.0", + "dependencies": { + "@a2a-js/sdk": "^0.3.10", + "@google/genai": "^1.37.0", + "@mikro-orm/core": "^6.6.10", + "@mikro-orm/reflection": "^6.6.6", + "@modelcontextprotocol/sdk": "^1.26.0", + "express": "^4.22.1", + "google-auth-library": "^10.3.0", + "lodash-es": "^4.17.23", + "winston": "^3.19.0", + "zod": "^4.2.1", + "zod-to-json-schema": "^3.25.1" + }, + "peerDependencies": { + "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", + "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", + "@google-cloud/storage": "^7.17.1", + "@mikro-orm/mariadb": "^6.6.6", + "@mikro-orm/mssql": "^6.6.6", + "@mikro-orm/mysql": "^6.6.6", + "@mikro-orm/postgresql": "^6.6.6", + "@mikro-orm/sqlite": "^6.6.6", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/api-logs": "^0.205.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.205.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.205.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.205.0", + "@opentelemetry/resource-detector-gcp": "^0.40.0", + "@opentelemetry/resources": "^2.1.0", + "@opentelemetry/sdk-logs": "^0.205.0", + "@opentelemetry/sdk-metrics": "^2.1.0", + "@opentelemetry/sdk-trace-base": "^2.1.0", + "@opentelemetry/sdk-trace-node": "^2.1.0" + } + }, + "node_modules/@google/adk-devtools": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@google/adk-devtools/-/adk-devtools-0.6.1.tgz", + "integrity": "sha512-0ImIs+JLHS5mBGklcecT1cptYeBFAWYQmAPN+OFwEbneRkEbGp4MksBBbXtLuiw33Wr888aE2+mE4H9VRunHTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@clack/prompts": "^0.11.0", + "@google/adk": "^0.6.1", + "@mikro-orm/mariadb": "^6.6.6", + "@mikro-orm/mssql": "^6.6.6", + "@mikro-orm/mysql": "^6.6.6", + "@mikro-orm/postgresql": "^6.6.6", + "@mikro-orm/sqlite": "^6.6.6", + "camelcase-keys": "^6.2.2", + "commander": "^14.0.0", + "cors": "^2.8.5", + "dotenv": "^17.2.3", + "esbuild": "^0.25.9", + "esbuild-shim-plugin": "^1.0.3", + "express": "^4.21.2", + "fast-glob": "^3.3.3", + "ts-graphviz": "^1.0.1", + "winston": "^3.19.0", + "yaml": "^2.8.3", + "zod": "^4.2.1" + }, + "bin": { + "adk": "dist/cli_entrypoint.mjs" + } + }, + "node_modules/@google/adk-devtools/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk-devtools/node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@google/adk-devtools/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk-devtools/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@google/adk-devtools/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@google/adk-devtools/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@google/adk-devtools/node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@google/adk-devtools/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@google/adk-devtools/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk-devtools/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@google/adk-devtools/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk-devtools/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@google/adk-devtools/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@google/adk-devtools/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk-devtools/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk-devtools/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk-devtools/node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@google/adk-devtools/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@google/adk-devtools/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@google/adk-devtools/node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@google/adk-devtools/node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@google/adk-devtools/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk/node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@google/adk/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/@google/adk/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@google/adk/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@google/adk/node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@google/adk/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@google/adk/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk/node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/adk/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/adk/node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/adk/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google/adk/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@google/adk/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@google/adk/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@google/adk/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/adk/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/@google/adk/node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/@google/adk/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@google/adk/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@google/adk/node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@google/adk/node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@google/adk/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@google/genai": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.50.1.tgz", + "integrity": "sha512-YbkX7H9+1Pt8wOt7DDREy8XSoiL6fRDzZQRyaVBarFf8MR3zHGqVdvM4cLbDXqPhxqvegZShgfxb8kw9C7YhAQ==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@google/genai/node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai/node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google/genai/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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/@js-joda/core": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.7.0.tgz", + "integrity": "sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg==", + "license": "BSD-3-Clause" + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@mikro-orm/core": { + "version": "6.6.13", + "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-6.6.13.tgz", + "integrity": "sha512-Zf00ZCUV1/fTCE60jJUDDbFb6dDYaojUWr0yoavNYJaFX+qoLdgKSj3tX6j2v//cGKfb/sLqs72FEEtSwvhviA==", + "license": "MIT", + "dependencies": { + "dataloader": "2.2.3", + "dotenv": "17.3.1", + "esprima": "4.0.1", + "fs-extra": "11.3.3", + "globby": "11.1.0", + "mikro-orm": "6.6.13", + "reflect-metadata": "0.2.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/b4nan" + } + }, + "node_modules/@mikro-orm/core/node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@mikro-orm/knex": { + "version": "6.6.13", + "resolved": "https://registry.npmjs.org/@mikro-orm/knex/-/knex-6.6.13.tgz", + "integrity": "sha512-2pnGow7WvGhJlRxgIX3JvY6qLK58X/vVFCCbHIHGVv2STW0vexFYw6o++f53jybaSJcFjiMcWFWsnzlR80xv3g==", + "license": "MIT", + "dependencies": { + "fs-extra": "11.3.3", + "knex": "3.2.8", + "sqlstring": "2.3.3" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0", + "better-sqlite3": "*", + "libsql": "*", + "mariadb": "*" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "libsql": { + "optional": true + }, + "mariadb": { + "optional": true + } + } + }, + "node_modules/@mikro-orm/mariadb": { + "version": "6.6.13", + "resolved": "https://registry.npmjs.org/@mikro-orm/mariadb/-/mariadb-6.6.13.tgz", + "integrity": "sha512-unKk0JjcFxUUPw6wm3lHm8dgHu17bTqTuFNIaXavv9useeIYD0DKpBHN1HZMfuXxrbyEwHXedQMdykp53W9Osg==", + "license": "MIT", + "dependencies": { + "@mikro-orm/knex": "6.6.13", + "mariadb": "3.4.5" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0" + } + }, + "node_modules/@mikro-orm/mssql": { + "version": "6.6.13", + "resolved": "https://registry.npmjs.org/@mikro-orm/mssql/-/mssql-6.6.13.tgz", + "integrity": "sha512-/GaDB/+CsbtFdtAuB1FxypS7U1ARkabFtkKpBLQOR2I/ixO84lbCZPUbt7yDRD+01Wtml+Zp5pkoQcN1/SGdng==", + "license": "MIT", + "dependencies": { + "@mikro-orm/knex": "6.6.13", + "tedious": "19.2.1", + "tsqlstring": "1.0.1" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0" + } + }, + "node_modules/@mikro-orm/mysql": { + "version": "6.6.13", + "resolved": "https://registry.npmjs.org/@mikro-orm/mysql/-/mysql-6.6.13.tgz", + "integrity": "sha512-OpLzpZKqGwLBJ8cnzJB/RejsKSv4vjvvjH9eJNVIl8Pjd+5UaBBn2HLLDaO3YJ8LE9RI1XEQZcXQ9lNiiPpzXw==", + "license": "MIT", + "dependencies": { + "@mikro-orm/knex": "6.6.13", + "mysql2": "3.20.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0" + } + }, + "node_modules/@mikro-orm/postgresql": { + "version": "6.6.13", + "resolved": "https://registry.npmjs.org/@mikro-orm/postgresql/-/postgresql-6.6.13.tgz", + "integrity": "sha512-kbQzwbQGTzNFe5IDcBQaaEVwJ0JnHDOprnIPxN1gYOFGmFa36bHPVf6jK+4hNhOJ1wUdSNj3EeDuadwtNpQgjg==", + "license": "MIT", + "dependencies": { + "@mikro-orm/knex": "6.6.13", + "pg": "8.20.0", + "postgres-array": "3.0.4", + "postgres-date": "2.1.0", + "postgres-interval": "4.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0" + } + }, + "node_modules/@mikro-orm/reflection": { + "version": "6.6.13", + "resolved": "https://registry.npmjs.org/@mikro-orm/reflection/-/reflection-6.6.13.tgz", + "integrity": "sha512-pH4jsGTRQ/jyC8WFhFtqvGWXUyNUqDUbPiL5DJU9lRKQH0AEeH15cYW1ZbfY85ECJSCAeR9qJsJo6gfFcl09vA==", + "license": "MIT", + "dependencies": { + "globby": "11.1.0", + "ts-morph": "27.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0" + } + }, + "node_modules/@mikro-orm/sqlite": { + "version": "6.6.13", + "resolved": "https://registry.npmjs.org/@mikro-orm/sqlite/-/sqlite-6.6.13.tgz", + "integrity": "sha512-J3KHvosRxjQfC6NhUj8GrstLvdi/47yLgtGPXzpNXl3B62giecyRRLUxK+rCzBXlcP4aAyyNFVeEjGhpN0tLhg==", + "license": "MIT", + "dependencies": { + "@mikro-orm/knex": "6.6.13", + "fs-extra": "11.3.3", + "sqlite3": "5.1.7", + "sqlstring-sqlite": "0.1.1" + }, + "engines": { + "node": ">= 18.12.0" + }, + "peerDependencies": { + "@mikro-orm/core": "^6.0.0" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@noble/ed25519": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.5.tgz", + "integrity": "sha512-xuS0nwRMQBvSxDa7UxMb61xTiH3MxTgUfhyPUALVIe0FlOAz4sjELwyDRyUvqeEYfRSG9qNjFIycqLZppg4RSA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@nodable/entities": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-1.1.0.tgz", + "integrity": "sha512-bidpxmTBP0pOsxULw6XlxzQpTgrAGLDHGBK/JuWhPDL6ZV0GZ/PmN9CA9do6e+A9lYI6qx6ikJUtJYRxup141g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.205.0.tgz", + "integrity": "sha512-wBlPk1nFB37Hsm+3Qy73yQSobVn28F4isnWIBvKpd5IUH/eat8bwcL02H9yzmHyyPmukeccSl2mbN5sDQZYnPg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.1.tgz", + "integrity": "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.205.0.tgz", + "integrity": "sha512-jQlw7OHbqZ8zPt+pOrW2KGN7T55P50e3NXBMr4ckPOF+DWDwSy4W7mkG09GpYWlQAQ5C9BXg5gfUlv5ldTgWsw==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.205.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.205.0", + "@opentelemetry/otlp-transformer": "0.205.0", + "@opentelemetry/sdk-logs": "0.205.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.205.0.tgz", + "integrity": "sha512-5JteMyVWiro4ghF0tHQjfE6OJcF7UBUcoEqX3UIQ5jutKP1H+fxFdyhqjjpmeHMFxzOHaYuLlNR1Bn7FOjGyJg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.205.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.205.0", + "@opentelemetry/otlp-transformer": "0.205.0", + "@opentelemetry/sdk-logs": "0.205.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.205.0.tgz", + "integrity": "sha512-q3VS9wS+lpZ01txKxiDGBtBpTNge3YhbVEFDgem9ZQR9eI3EZ68+9tVZH9zJcSxI37nZPJ6lEEZO58yEjYZsVA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.205.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.205.0", + "@opentelemetry/otlp-transformer": "0.205.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-logs": "0.205.0", + "@opentelemetry/sdk-trace-base": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.205.0.tgz", + "integrity": "sha512-1Vxlo4lUwqSKYX+phFkXHKYR3DolFHxCku6lVMP1H8sVE3oj4wwmwxMzDsJ7zF+sXd8M0FCr+ckK4SnNNKkV+w==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.205.0", + "@opentelemetry/otlp-exporter-base": "0.205.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.205.0", + "@opentelemetry/otlp-transformer": "0.205.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-metrics": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.1.0.tgz", + "integrity": "sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.205.0.tgz", + "integrity": "sha512-fFxNQ/HbbpLmh1pgU6HUVbFD1kNIjrkoluoKJkh88+gnmpFD92kMQ8WFNjPnSbjg2mNVnEkeKXgCYEowNW+p1w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.205.0", + "@opentelemetry/otlp-transformer": "0.205.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-metrics": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.1.0.tgz", + "integrity": "sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.205.0.tgz", + "integrity": "sha512-qIbNnedw9QfFjwpx4NQvdgjK3j3R2kWH/2T+7WXAm1IfMFe9fwatYxE61i7li4CIJKf8HgUC3GS8Du0C3D+AuQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.205.0", + "@opentelemetry/otlp-exporter-base": "0.205.0", + "@opentelemetry/otlp-transformer": "0.205.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-metrics": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.1.0.tgz", + "integrity": "sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.205.0.tgz", + "integrity": "sha512-xsot/Qm9VLDTag4GEwAunD1XR1U8eBHTLAgO7IZNo2JuD/c/vL7xmDP7mQIUr6Lk3gtj/yGGIR2h3vhTeVzv4w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-metrics": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.1.0.tgz", + "integrity": "sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.205.0.tgz", + "integrity": "sha512-ZBksUk84CcQOuDJB65yu5A4PORkC4qEsskNwCrPZxDLeWjPOFZNSWt0E0jQxKCY8PskLhjNXJYo12YaqsYvGFA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.205.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.205.0", + "@opentelemetry/otlp-transformer": "0.205.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-trace-base": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.205.0.tgz", + "integrity": "sha512-vr2bwwPCSc9u7rbKc74jR+DXFvyMFQo9o5zs+H/fgbK672Whw/1izUKVf+xfWOdJOvuwTnfWxy+VAY+4TSo74Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.205.0", + "@opentelemetry/otlp-transformer": "0.205.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-trace-base": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.205.0.tgz", + "integrity": "sha512-bGtFzqiENO2GpJk988mOBMe0MfeNpTQjbLm/LBijas6VRyEDQarUzdBHpFlu89A25k1+BCntdWGsWTa9Ai4FyA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.205.0", + "@opentelemetry/otlp-transformer": "0.205.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-trace-base": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.1.0.tgz", + "integrity": "sha512-0mEI0VDZrrX9t5RE1FhAyGz+jAGt96HSuXu73leswtY3L5YZD11gtcpARY2KAx/s6Z2+rj5Mhj566JsI2C7mfA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-trace-base": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.205.0.tgz", + "integrity": "sha512-cgvm7tvQdu9Qo7VurJP84wJ7ZV9F6WqDDGZpUc6rUEXwjV7/bXWs0kaYp9v+1Vh1+3TZCD3i6j/lUBcPhu8NhA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.205.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.205.0.tgz", + "integrity": "sha512-2MN0C1IiKyo34M6NZzD6P9Nv9Dfuz3OJ3rkZwzFmF6xzjDfqqCTatc9v1EpNfaP55iDOCLHFyYNCgs61FFgtUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-transformer": "0.205.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.205.0.tgz", + "integrity": "sha512-AeuLfrciGYffqsp4EUTdYYc6Ee2BQS+hr08mHZk1C524SFWx0WnfcTnV0NFXbVURUNU6DZu1DhS89zRRrcx/hg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.205.0", + "@opentelemetry/otlp-transformer": "0.205.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.205.0.tgz", + "integrity": "sha512-KmObgqPtk9k/XTlWPJHdMbGCylRAmMJNXIRh6VYJmvlRDMfe+DonH41G7eenG8t4FXn3fxOGh14o/WiMRR6vPg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.205.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-logs": "0.205.0", + "@opentelemetry/sdk-metrics": "2.1.0", + "@opentelemetry/sdk-trace-base": "2.1.0", + "protobufjs": "^7.3.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.1.0.tgz", + "integrity": "sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.1.0.tgz", + "integrity": "sha512-yOdHmFseIChYanddMMz0mJIFQHyjwbNhoxc65fEAA8yanxcBPwoFDoh1+WBUWAO/Z0NRgk+k87d+aFIzAZhcBw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.1.0.tgz", + "integrity": "sha512-QYo7vLyMjrBCUTpwQBF/e+rvP7oGskrSELGxhSvLj5gpM0az9oJnu/0O4l2Nm7LEhAff80ntRYKkAcSwVgvSVQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resource-detector-gcp": { + "version": "0.40.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.40.3.tgz", + "integrity": "sha512-C796YjBA5P1JQldovApYfFA/8bQwFfpxjUbOtGhn1YZkVTLoNQN+kvBwgALfTPWzug6fWsd0xhn9dzeiUcndag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "gcp-metadata": "^6.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", + "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.205.0.tgz", + "integrity": "sha512-nyqhNQ6eEzPWQU60Nc7+A5LIq8fz3UeIzdEVBQYefB4+msJZ2vuVtRuk9KxPMw1uHoHDtYEwkr2Ct0iG29jU8w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.205.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.1.tgz", + "integrity": "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.205.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.205.0.tgz", + "integrity": "sha512-Y4Wcs8scj/Wy1u61pX1ggqPXPtCsGaqx/UnFu7BtRQE1zCQR+b0h56K7I0jz7U2bRlPUZIFdnNLtoaJSMNzz2g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.205.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.205.0", + "@opentelemetry/exporter-logs-otlp-http": "0.205.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.205.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.205.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.205.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.205.0", + "@opentelemetry/exporter-prometheus": "0.205.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.205.0", + "@opentelemetry/exporter-trace-otlp-http": "0.205.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.205.0", + "@opentelemetry/exporter-zipkin": "2.1.0", + "@opentelemetry/instrumentation": "0.205.0", + "@opentelemetry/propagator-b3": "2.1.0", + "@opentelemetry/propagator-jaeger": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-logs": "0.205.0", + "@opentelemetry/sdk-metrics": "2.1.0", + "@opentelemetry/sdk-trace-base": "2.1.0", + "@opentelemetry/sdk-trace-node": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/context-async-hooks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.1.0.tgz", + "integrity": "sha512-zOyetmZppnwTyPrt4S7jMfXiSX9yyfF0hxlA8B5oo2TtKl+/RGCy7fi4DrBfIf3lCPrkKsRBWZZD7RFojK7FDg==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.1.0.tgz", + "integrity": "sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.1.0.tgz", + "integrity": "sha512-SvVlBFc/jI96u/mmlKm86n9BbTCbQ35nsPoOohqJX6DXH92K0kTe73zGY5r8xoI1QkjR9PizszVJLzMC966y9Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.1.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/sdk-trace-base": "2.1.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.1.tgz", + "integrity": "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.6.1.tgz", + "integrity": "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.6.1", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/sdk-trace-base": "2.6.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", + "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "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.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "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.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "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/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.28.1.tgz", + "integrity": "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==", + "license": "MIT", + "dependencies": { + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1", + "tinyglobby": "^0.2.14" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT" + }, + "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/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/readable-stream": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz", + "integrity": "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/request": { + "version": "2.48.13", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz", + "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", + "license": "MIT", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.5" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", + "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/type-utils": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.58.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", + "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", + "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.5.tgz", + "integrity": "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw==", + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@typespec/ts-http-runtime/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vitest/expect": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base58-universal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base58-universal/-/base58-universal-2.0.0.tgz", + "integrity": "sha512-BgkgF8zVLOAygszG4W8NkLm7iXrw80VYAOcedrzANrIhS14+4W6zVqjyGTFUBM/FpqkHUt8aAYd4DbBBfn3zKg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=14" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/base64url-universal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64url-universal/-/base64url-universal-2.0.0.tgz", + "integrity": "sha512-6Hpg7EBf3t148C3+fMzjf+CHnADVDafWzlJUXAqqqbm4MKNXbsoPdOkWeRTjNlkYG7TpyjIpRO1Gk0SnsFD1rw==", + "license": "BSD-3-Clause", + "dependencies": { + "base64url": "^3.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/canonicalize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.1.0.tgz", + "integrity": "sha512-F705O3xrsUtgt98j7leetNhTWPe+5S72rlL5O4jA1pKqBVQ/dT1O1D6PFxmSXvc0SUOinWS57DKx0I3CHrXJHQ==", + "license": "Apache-2.0", + "bin": { + "canonicalize": "bin/canonicalize.js" + } + }, + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "license": "MIT" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "license": "MIT" + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "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", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-ld": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/crypto-ld/-/crypto-ld-7.0.0.tgz", + "integrity": "sha512-RrXy6aB0TOhSiqsgavTQt1G8mKomKIaNLb2JZxj7A/Vi0EwmXguuBQoeiAvePfK6bDR3uQbqYnaLLs4irTWwgw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=14" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dataloader": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", + "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ed25519-signature-2020-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ed25519-signature-2020-context/-/ed25519-signature-2020-context-1.1.0.tgz", + "integrity": "sha512-dBGSmoUIK6h2vadDctrDnhhTO01PR2hJk0mRNEfrRDPCjaIwrfy4J+eziEQ9Q1m8By4f/CSRgKM1h53ydKfdNg==", + "license": "BSD-3-Clause" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", + "optional": true + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esbuild-shim-plugin": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/esbuild-shim-plugin/-/esbuild-shim-plugin-1.0.3.tgz", + "integrity": "sha512-FEwQnCEk6tWw+27QYCBSICrfMcaENFiX75MGsYPKM+FG6aw6dihJ3TNJGAiMPOcVb9bqycZkcCTe+DurBNW9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.9" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz", + "integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "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/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", + "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.6.0.tgz", + "integrity": "sha512-5G+uaEBbOm9M4dgMOV3K/rBzfUNGqGqoUTaYJM3hBwM8t71w07gxLQZoTsjkY8FtfjabqgQHEkeIySBDYeBmJw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^1.1.0", + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/getopts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", + "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==", + "license": "MIT" + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", + "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "137.1.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-137.1.0.tgz", + "integrity": "sha512-2L7SzN0FLHyQtFmyIxrcXhgust77067pkkduqkbIpDuj9JzVnByxsRrcRfUMFQam3rQkWW2B0f1i40IwKDWIVQ==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.14", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz", + "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-in-the-middle": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", + "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", + "optional": true + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonld": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-9.0.0.tgz", + "integrity": "sha512-pjMIdkXfC1T2wrX9B9i2uXhGdyCmgec3qgMht+TDj+S0qX3bjWMQUfL7NeqEhuRTi8G5ESzmL9uGlST7nzSEWg==", + "license": "BSD-3-Clause", + "dependencies": { + "@digitalbazaar/http-client": "^4.2.0", + "canonicalize": "^2.1.0", + "lru-cache": "^6.0.0", + "rdf-canonize": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsonld-signatures": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/jsonld-signatures/-/jsonld-signatures-11.6.0.tgz", + "integrity": "sha512-hzYNZXnfy4cUFf9aiFBtduUz+cknbfBLWtTKvoqVyP2ECPwqfsfkHWFlhccWfAKV/LJkPLyKZRwC1B4T5LO4ZQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@digitalbazaar/security-context": "^1.0.0", + "jsonld": "^9.0.0", + "rdf-canonize": "^5.0.0", + "serialize-error": "^8.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/knex": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/knex/-/knex-3.2.8.tgz", + "integrity": "sha512-ElXXxu9Nq+5hWYdBUddYIWIT5yKKs5KNCsmKGbJSHPyaMpAABp3xs4L55GgdQoAs6QQ7dv72ai3M4pxYQ8utEg==", + "license": "MIT", + "dependencies": { + "colorette": "2.0.19", + "commander": "^10.0.0", + "debug": "4.3.4", + "escalade": "^3.1.1", + "esm": "^3.2.25", + "get-package-type": "^0.1.0", + "getopts": "2.3.0", + "interpret": "^2.2.0", + "lodash": "^4.17.21", + "pg-connection-string": "2.6.2", + "rechoir": "^0.8.0", + "resolve-from": "^5.0.0", + "tarn": "^3.0.2", + "tildify": "2.0.0" + }, + "bin": { + "knex": "bin/cli.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "pg-query-stream": "^4.14.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "mysql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/knex/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/knex/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/ky": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", + "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "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/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mariadb": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz", + "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==", + "license": "LGPL-2.1-or-later", + "dependencies": { + "@types/geojson": "^7946.0.16", + "@types/node": "^24.0.13", + "denque": "^2.1.0", + "iconv-lite": "^0.6.3", + "lru-cache": "^10.4.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/mariadb/node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/mariadb/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mariadb/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/mariadb/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mikro-orm": { + "version": "6.6.13", + "resolved": "https://registry.npmjs.org/mikro-orm/-/mikro-orm-6.6.13.tgz", + "integrity": "sha512-o7HtpBllUIjWwBQB8sQ/0MBJ90vqBM4fzmb2IeRScgWVtZgpcqDOEscuOgfxMFfaaUfA14meNdE8mxsre8JjKg==", + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.20.0.tgz", + "integrity": "sha512-eCLUs7BNbgA6nf/MZXsaBO1SfGs0LtLVrJD3WeWq+jPLDWkSufTD+aGMwykfUVPdZnblaUK1a8G/P63cl9FkKg==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.2", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" + }, + "engines": { + "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/native-duplexpair": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", + "integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pg-types/node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pg/node_modules/pg-connection-string": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "license": "MIT" + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "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": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-interval": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-4.0.2.tgz", + "integrity": "sha512-EMsphSQ1YkQqKZL2cuG0zHkmjCCzQqQ71l2GXITqRwjhRleCdv00bDk/ktaSi0LnlaPzAc3535KTrjXsTdtx7A==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/protobufjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", + "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rdf-canonize": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-5.0.0.tgz", + "integrity": "sha512-g8OUrgMXAR9ys/ZuJVfBr05sPPoMA7nHIVs8VEvg9QwM5W4GR2qSFEEHjsyHF1eWlBaf8Ev40WNjQFQ+nJTO3w==", + "license": "BSD-3-Clause", + "dependencies": { + "setimmediate": "^1.0.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rechoir/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/require-in-the-middle/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "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/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, + "node_modules/sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" + } + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/sqlstring-sqlite": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sqlstring-sqlite/-/sqlstring-sqlite-0.1.1.tgz", + "integrity": "sha512-9CAYUJ0lEUPYJrswqiqdINNSfq3jqWo/bFJ7tufdoNeSK0Fy+d1kFTxjqO9PIqza0Kri+ZtYMfPVf1aZaFOvrQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/tedious": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/tedious/-/tedious-19.2.1.tgz", + "integrity": "sha512-pk1Q16Yl62iocuQB+RWbg6rFUFkIyzqOFQ6NfysCltRvQqKwfurgj8v/f2X+CKvDhSL4IJ0cCOfCHDg9PWEEYA==", + "license": "MIT", + "dependencies": { + "@azure/core-auth": "^1.7.2", + "@azure/identity": "^4.2.1", + "@azure/keyvault-keys": "^4.4.0", + "@js-joda/core": "^5.6.5", + "@types/node": ">=18", + "bl": "^6.1.4", + "iconv-lite": "^0.7.0", + "js-md4": "^0.3.2", + "native-duplexpair": "^1.0.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">=18.17" + } + }, + "node_modules/tedious/node_modules/bl": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", + "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", + "license": "MIT", + "dependencies": { + "@types/readable-stream": "^4.0.0", + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + } + }, + "node_modules/tedious/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/tedious/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/teeny-request/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/teeny-request/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/tildify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", + "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "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/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "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/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-graphviz": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/ts-graphviz/-/ts-graphviz-1.8.2.tgz", + "integrity": "sha512-5YhbFoHmjxa7pgQLkB07MtGnGJ/yhvjmc9uhsnDBEICME6gkPf83SBwLDQqGDoCa3XzUMWLk1AU2Wn1u1naDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + } + }, + "node_modules/ts-morph": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-27.0.2.tgz", + "integrity": "sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==", + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.28.1", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "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==", + "license": "0BSD" + }, + "node_modules/tsqlstring": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tsqlstring/-/tsqlstring-1.0.1.tgz", + "integrity": "sha512-6Nzj/SrVg1SF+egwP4OMAgEa83nLKXIE3EHn+6YKinMUeMj8bGIeLuDCkDC3Cc4OIM+xhw4CD0oXKxal8J/Y6A==", + "license": "MIT", + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", + "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.2", + "@typescript-eslint/parser": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vitest": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "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.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", + "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/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.4", + "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/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/vitest/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/vitest/node_modules/vite": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" + }, + "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.1.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/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "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" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/samples/typescript/package.json b/samples/typescript/package.json new file mode 100644 index 00000000..7ac4a066 --- /dev/null +++ b/samples/typescript/package.json @@ -0,0 +1,73 @@ +{ + "name": "ap2-typescript-samples", + "version": "0.1.0", + "description": "TypeScript samples for the Agent Payments Protocol (AP2).", + "type": "module", + "scripts": { + "build": "tsc", + "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "cli": "npx adk run src/roles/shopping/agent.ts", + "dev": "concurrently \"npx tsx src/roles/shopping/server.ts\" \"npx tsx src/roles/credentials-provider/server.ts\" \"npx tsx src/roles/payment-processor/server.ts\" \"npx tsx src/roles/merchant/server.ts\" \"npx adk web src/roles/shopping --host localhost -p 3001\"" + }, + "keywords": [ + "ap2", + "agent-payments-protocol", + "a2a", + "adk", + "agents" + ], + "author": "Google LLC", + "license": "Apache-2.0", + "engines": { + "node": ">=20.0.0" + }, + "dependencies": { + "@a2a-js/sdk": "^0.3.13", + "@digitalbazaar/ed25519-signature-2020": "^5.4.0", + "@digitalbazaar/ed25519-verification-key-2020": "^4.2.0", + "@digitalbazaar/vc": "^7.3.0", + "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", + "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", + "@google-cloud/storage": "^7.19.0", + "@google/adk": "^0.6.1", + "@google/genai": "^1.37.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.205.0", + "@opentelemetry/core": "^2.1.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.205.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.205.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.205.0", + "@opentelemetry/resource-detector-gcp": "^0.40.0", + "@opentelemetry/resources": "^2.1.0", + "@opentelemetry/sdk-logs": "^0.205.0", + "@opentelemetry/sdk-metrics": "^2.1.0", + "@opentelemetry/sdk-node": "^0.205.0", + "@opentelemetry/sdk-trace-base": "^2.1.0", + "@opentelemetry/sdk-trace-node": "^2.1.0", + "dotenv": "^17.4.2", + "express": "^5.1.0", + "uuid": "^13.0.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@google/adk-devtools": "^0.6.1", + "@types/express": "^5.0.6", + "@types/node": "^25.0.10", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^8.53.1", + "@typescript-eslint/parser": "^8.53.1", + "concurrently": "^9.1.2", + "eslint": "^9.39.4", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.32.0", + "globals": "^17.5.0", + "tsx": "^4.21.0", + "typescript": "^5.7.2", + "typescript-eslint": "^8.58.2", + "vitest": "^4.1.4" + } +} diff --git a/samples/typescript/scenarios/a2a/human-present/cards/README.md b/samples/typescript/scenarios/a2a/human-present/cards/README.md new file mode 100644 index 00000000..af68d697 --- /dev/null +++ b/samples/typescript/scenarios/a2a/human-present/cards/README.md @@ -0,0 +1,125 @@ +# TypeScript Sample: Human-Present Card Payment (A2A) + +This scenario demonstrates the A2A `ap2-extension` for a human-present +transaction using a card as the payment method, implemented entirely in +TypeScript. + +## Scenario + +Human-Present flows refer to all commerce flows where the user is present to +confirm the details of what is being purchased and what payment method is to be +used. The user attesting to the details of the purchase allows all parties to +have high confidence of the transaction. + +The IntentMandate is leveraged to share the appropriate information with +Merchant Agents. All Human-Present purchases will have a user-signed +PaymentMandate authorizing the purchase. + +## Agents Implemented + +All four roles are implemented in TypeScript: + +- **Shopping Agent** (`http://localhost:8001`) + - Root orchestrator running on the ADK web UI. + - Coordinates `shopper`, `shipping_collector`, and `payment_collector` + sub_agents. +- **Merchant Agent** (`http://localhost:8004/a2a/merchant_agent`) + - Handles product catalog queries and creates signed CartMandates. +- **Credentials Provider** (`http://localhost:8002/a2a/credentials_provider`) + - Manages payment credentials and provides DPAN tokens. +- **Merchant Payment Processor** (`http://localhost:8003/a2a/merchant_payment_processor_agent`) + - Authorizes payments and runs the OTP challenge. + +## What This Sample Demonstrates + +1. **AP2 Protocol Features** + - Complete mandate lifecycle (Intent → Cart → Payment → Receipt) + - Card payment with DPAN tokens + - OTP challenge during payment authorization + - W3C Verifiable Credential signing of mandates +2. **TypeScript Patterns** + - Zod schemas mirroring the AP2 types + - ADK `LlmAgent` orchestration with `FunctionTool`s + - A2A request/response handling via `@a2a-js/sdk` + +## Setup + +### Prerequisites + +- Node.js 18+ +- npm + +### Configure credentials + +Obtain a Google API key from +[Google AI Studio](https://aistudio.google.com/apikey), then create a `.env` +file in `samples/typescript/`: + +```sh +cp samples/typescript/.env.example samples/typescript/.env +# Edit samples/typescript/.env and fill in GOOGLE_API_KEY +``` + +Alternatively, configure Vertex AI by setting `GOOGLE_GENAI_USE_VERTEXAI=true` +along with `GOOGLE_CLOUD_PROJECT` and `GOOGLE_CLOUD_LOCATION`. + +### Install dependencies + +```sh +cd samples/typescript +npm install +``` + +## Execution + +### Option 1: Run everything with one command + +```sh +bash samples/typescript/scenarios/a2a/human-present/cards/run.sh +``` + +This starts the three backend agents and the Shopping Agent web UI. + +### Option 2: Run each agent in its own terminal + +```sh +# Terminal 1: Merchant Agent (port 8004) +npx tsx src/roles/merchant/server.ts + +# Terminal 2: Credentials Provider (port 8002) +npx tsx src/roles/credentials-provider/server.ts + +# Terminal 3: Payment Processor (port 8003) +npx tsx src/roles/payment-processor/server.ts + +# Terminal 4: Shopping Agent A2A server (port 8001) +npx tsx src/roles/shopping/server.ts + +# Terminal 5: ADK web UI (port 3001) +npx adk web src/roles/shopping --host localhost -p 3001 +``` + +Then open the ADK web UI at and select the +`shopping_agent`. + +## Interacting with the Shopping Agent + +1. **Initial request**: Type something like _"I want to buy running shoes."_ +2. **Product search**: The Shopping Agent delegates to the Merchant Agent, + which returns CartMandate options. +3. **Product selection**: Choose one of the offered carts. +4. **Shipping address**: The `shipping_collector` sub_agent prompts for an + address; the cart is then re-signed by the merchant. +5. **Payment method**: The `payment_collector` sub_agent fetches available + payment methods from the Credentials Provider. +6. **OTP challenge**: The Payment Processor issues an OTP challenge that you + complete in the chat. +7. **Receipt**: A signed `PaymentReceipt` is returned and displayed. + +## Smoke Tests + +After the agents are running, you can run end-to-end smoke tests: + +```sh +npm run test:e2e +``` diff --git a/samples/typescript/scenarios/a2a/human-present/cards/run.sh b/samples/typescript/scenarios/a2a/human-present/cards/run.sh new file mode 100755 index 00000000..0aab8e66 --- /dev/null +++ b/samples/typescript/scenarios/a2a/human-present/cards/run.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +SAMPLE_DIR="$(cd "$(dirname "$0")/../../../.." && pwd)" +cd "$SAMPLE_DIR" + +echo "==========================================" +echo "AP2 TypeScript Sample - Human-Present Cards" +echo "==========================================" + +if [ -f .env ]; then + set -a + # shellcheck disable=SC1091 + source .env + set +a +fi + +USE_VERTEXAI=$(printf "%s" "${GOOGLE_GENAI_USE_VERTEXAI}" | tr '[:upper:]' '[:lower:]') +if [ -z "${GOOGLE_API_KEY}" ] && [ "${USE_VERTEXAI}" != "true" ]; then + echo "Error: GOOGLE_API_KEY is not set." + echo "Either export GOOGLE_API_KEY or set GOOGLE_GENAI_USE_VERTEXAI=true." + echo "See ${SAMPLE_DIR}/.env.example for reference." + exit 1 +fi + +if [ ! -d node_modules ]; then + echo "Installing dependencies..." + npm install +fi + +echo "" +echo "Starting all agents (merchant, credentials, payment processor) and the" +echo "Shopping Agent web UI on http://localhost:3001 ..." +echo "Press Ctrl+C to stop." +echo "" + +exec npm run dev diff --git a/samples/typescript/src/common/config/session.ts b/samples/typescript/src/common/config/session.ts new file mode 100644 index 00000000..46f40855 --- /dev/null +++ b/samples/typescript/src/common/config/session.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import 'dotenv/config'; +import { InMemorySessionService } from '@google/adk'; + +/** + * Global session service for ADK agents. + * + * Each agent server process gets its own instance (since ES modules are + * per-process). In ADK dev mode (`npm run dev`), all agents share this + * single instance within the same process, which is the intended behavior + * for local development. + */ +export const sessionService = new InMemorySessionService(); diff --git a/samples/typescript/src/common/constants/index.ts b/samples/typescript/src/common/constants/index.ts new file mode 100644 index 00000000..49b51f67 --- /dev/null +++ b/samples/typescript/src/common/constants/index.ts @@ -0,0 +1,48 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** AP2 mandate data keys */ +export const DATA_KEYS = { + CART_MANDATE: "ap2.mandates.CartMandate", + INTENT_MANDATE: "ap2.mandates.IntentMandate", + PAYMENT_MANDATE: "ap2.mandates.PaymentMandate", + PAYMENT_RECEIPT: "ap2.PaymentReceipt", + CONTACT_ADDRESS: "contact_picker.ContactAddress", +} as const; + +/** A2A protocol data part keys */ +export const A2A_DATA_KEYS = { + PAYMENT_METHOD_DATA: "payment_request.PaymentMethodData", + RISK_DATA: "risk_data", + USER_EMAIL: "user_email", + PAYMENT_METHOD_ALIAS: "payment_method_alias", + CHALLENGE_RESPONSE: "challenge_response", + CART_ID: "cart_id", + SHIPPING_ADDRESS: "shipping_address", + DPC_RESPONSE: "dpc_response", + DEBUG_MODE: "debug_mode", + SHOPPING_AGENT_ID: "shopping_agent_id", +} as const; + +export const DEBUG_MODE_INSTRUCTIONS = ` + This is really important! If the agent or user asks you to be verbose or if debug_mode is True, do the following: + 1. If this is the the start of a new task, explain who you are, what you are going to do, what tools you use, and what agents you delegate to. + 2. During the task, provide regular status updates on what you are doing, what you have done so far, and what you plan to do next. + 3. If you are delegating to another agent, ask the agent or tool to also be verbose. + 4. If at any point in the task you send or receive data, show the data in a clear, formatted way. Do not summarize it in english. Simple format the JSON objects. + 5. Step 4 is so important that I'm going to repeat it: + a. If at any point in the task you create, send or receive data, show the data in a clear, formatted way. Do not summarize it in english. Simple format the JSON objects. +`; diff --git a/samples/typescript/src/common/schemas/cart-mandate.ts b/samples/typescript/src/common/schemas/cart-mandate.ts new file mode 100644 index 00000000..6d4e75e4 --- /dev/null +++ b/samples/typescript/src/common/schemas/cart-mandate.ts @@ -0,0 +1,192 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from "zod"; +import { shippingAddressSchema } from "./shipping-address.js"; + +export const cartMandateSchema = z.object({ + contents: z.object({ + id: z.string().describe("The ID of the cart mandate."), + userCartConfirmationRequired: z + .boolean() + .describe("If the user must confirm the cart."), + paymentRequest: z + .object({ + methodData: z.array( + z.object({ + supportedMethods: z.string().describe("The supported methods."), + data: z.record(z.string(), z.any()).describe("The data of the method."), + }) + ), + details: z.object({ + id: z.string().describe("The ID of the payment request."), + displayItems: z.array( + z.object({ + label: z.string().describe("The label of the display item."), + amount: z.object({ + currency: z + .string() + .describe("The three-letter ISO 4217 currency code."), + value: z.number().describe("The monetary value."), + }), + pending: z + .boolean() + .optional() + .describe("If true, indicates the amount is not final."), + refundPeriod: z + .number() + .describe("The refund duration for this item, in days."), + }) + ), + shippingOptions: z.array( + z.object({ + id: z.string().describe("The ID of the shipping option."), + label: z.string().describe("The label of the shipping option."), + amount: z.object({ + currency: z + .string() + .describe("The three-letter ISO 4217 currency code."), + value: z.number().describe("The monetary value."), + }), + selected: z + .boolean() + .optional() + .describe("If the shipping option is selected."), + }) + ).optional(), + modifiers: z.array( + z.object({ + supportedMethods: z + .string() + .describe( + "The payment method ID that this modifier applies to." + ), + total: z.object({ + label: z.string().describe("The label of the total."), + amount: z.object({ + currency: z + .string() + .describe("The three-letter ISO 4217 currency code."), + value: z.number().describe("The monetary value."), + }), + pending: z + .boolean() + .optional() + .describe("If true, indicates the amount is not final."), + refundPeriod: z + .number() + .describe("The refund duration for this item, in days."), + }).optional(), + additionalDisplayItems: z.array( + z.object({ + label: z + .string() + .describe("The label of the additional display item."), + amount: z.object({ + currency: z + .string() + .describe("The three-letter ISO 4217 currency code."), + value: z.number().describe("The monetary value."), + }), + pending: z + .boolean() + .optional() + .describe("If true, indicates the amount is not final."), + refundPeriod: z + .number() + .describe("The refund duration for this item, in days."), + }) + ).optional(), + data: z.record(z.string(), z.any()).optional().describe("The data of the modifier."), + }) + ).optional(), + total: z.object({ + label: z + .string() + .describe("A human-readable description of the item."), + amount: z.object({ + currency: z + .string() + .describe("The three-letter ISO 4217 currency code."), + value: z.number().describe("The monetary value."), + }), + pending: z.boolean().optional().describe("If the amount is not final."), + refundPeriod: z + .number() + .default(30) + .describe("The refund duration for this item, in days."), + }), + }), + options: z.object({ + requestPayerName: z + .boolean() + .describe("If the payer's name should be collected.") + .optional(), + requestPayerEmail: z + .boolean() + .describe("If the payer's email should be collected.") + .optional(), + requestPayerPhone: z + .boolean() + .describe("If the payer's phone number should be collected.") + .optional(), + requestShipping: z + .boolean() + .describe("If the payer's shipping address should be collected.") + .optional(), + shippingType: z + .enum(["shipping", "delivery", "pickup"]) + .optional() + .describe("Can be `shipping`, `delivery`, or `pickup`."), + }), + shippingAddress: shippingAddressSchema + .optional() + .describe("The shipping address of the user."), + }) + .describe( + "The W3C PaymentRequest object to initiate payment. This contains the" + + "items being purchased, prices, and the set of payment methods" + + "accepted by the merchant for this cart." + ), + cartExpiry: z + .string() + .describe("When this cart expires, in ISO 8601 format."), + merchantName: z.string().describe("The name of the merchant."), + }), + merchantAuthorization: z + .string() + .describe( + ` + A base64url-encoded JSON Web Token (JWT) that digitally + signs the cart contents, guaranteeing its authenticity and integrity: + 1. Header includes the signing algorithm and key ID. + 2. Payload includes: + - iss, sub, aud: Identifiers for the merchant (issuer) + and the intended recipient (audience), like a payment processor. + - iat: iat, exp: Timestamps for the token's creation and its + short-lived expiration (e.g., 5-15 minutes) to enhance security. + - jti: Unique identifier for the JWT to prevent replay attacks. + - cart_hash: A secure hash of the CartMandate, ensuring + integrity. The hash is computed over the canonical JSON + representation of the CartContents object. + 3. Signature: A digital signature created with the merchant's private + key. It allows anyone with the public key to verify the token's + authenticity and confirm that the payload has not been tampered with. + The entire JWT is base64url encoded to ensure safe transmission. + ` + ) + .optional(), +}); diff --git a/samples/typescript/src/common/schemas/intent-mandate.ts b/samples/typescript/src/common/schemas/intent-mandate.ts new file mode 100644 index 00000000..ec9daa04 --- /dev/null +++ b/samples/typescript/src/common/schemas/intent-mandate.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from "zod"; + +export const intentMandateSchema = z.object({ + naturalLanguageDescription: z.string(), + userCartConfirmationRequired: z.boolean(), + merchants: z.array(z.string()).optional(), + skus: z.array(z.string()).optional(), + requiresRefundability: z.boolean().optional(), + intentExpiry: z.string(), +}); diff --git a/samples/typescript/src/common/schemas/payment-mandate.ts b/samples/typescript/src/common/schemas/payment-mandate.ts new file mode 100644 index 00000000..0761f8f4 --- /dev/null +++ b/samples/typescript/src/common/schemas/payment-mandate.ts @@ -0,0 +1,93 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from "zod"; +import { shippingAddressSchema } from "./shipping-address.js"; + +export const paymentMandateSchema = z.object({ + paymentMandateContents: z.object({ + paymentMandateId: z + .string() + .describe("A unique identifier for this payment mandate."), + paymentDetailsId: z + .string() + .describe("A unique identifier for the payment request."), + paymentDetailsTotal: z + .object({ + label: z.string().describe("A human-readable description of the item."), + amount: z.object({ + currency: z + .string() + .describe("The three-letter ISO 4217 currency code."), + value: z.number().describe("The monetary value."), + }), + pending: z + .boolean() + .optional() + .describe("If true, indicates the amount is not final."), + refundPeriod: z + .number() + .default(30) + .describe("The refund duration for this item, in days."), + }) + .describe("The total payment amount."), + paymentResponse: z + .object({ + requestId: z + .string() + .describe("The unique ID from the original PaymentRequest."), + methodName: z + .string() + .describe("The payment method chosen by the user."), + details: z + .record(z.string(), z.unknown()) + .optional() + .describe( + "A dictionary generated by a payment method that a merchant can use to process a transaction." + ), + shippingAddress: shippingAddressSchema.optional(), + shippingOption: z + .object({ + id: z.string(), + label: z.string(), + amount: z.object({ + currency: z.string(), + value: z.number(), + }), + selected: z.boolean().optional(), + }) + .optional(), + payerName: z.string().optional(), + payerEmail: z.string().optional(), + payerPhone: z.string().optional(), + }) + .describe( + "The payment response containing details of the payment method chosen by the user." + ), + merchantAgent: z.string().describe("Identifier for the merchant."), + timestamp: z + .string() + .describe( + "The date and time the mandate was created, in ISO 8601 format." + ), + }), + userAuthorization: z + .string() + .optional() + .describe( + "A base64url-encoded verifiable presentation signing over the cart and payment mandate hashes." + ), +}); diff --git a/samples/typescript/src/common/schemas/payment-receipt.ts b/samples/typescript/src/common/schemas/payment-receipt.ts new file mode 100644 index 00000000..8ef067fe --- /dev/null +++ b/samples/typescript/src/common/schemas/payment-receipt.ts @@ -0,0 +1,74 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from "zod"; + +export const paymentSuccessSchema = z.object({ + kind: z.literal("success"), + merchantConfirmationId: z + .string() + .describe("A unique identifier for the transaction confirmation at the merchant."), + pspConfirmationId: z + .string() + .optional() + .describe("A unique identifier for the transaction confirmation at the PSP."), + networkConfirmationId: z + .string() + .optional() + .describe("A unique identifier for the transaction confirmation at the network."), +}); + +export const paymentErrorSchema = z.object({ + kind: z.literal("error"), + errorMessage: z + .string() + .describe("A human-readable message explaining the error and how to proceed."), +}); + +export const paymentFailureSchema = z.object({ + kind: z.literal("failure"), + failureMessage: z + .string() + .describe("A human-readable message explaining the failure and how to proceed."), +}); + +export const paymentStatusSchema = z.discriminatedUnion("kind", [ + paymentSuccessSchema, + paymentErrorSchema, + paymentFailureSchema, +]); + +export const paymentReceiptSchema = z.object({ + paymentMandateId: z + .string() + .describe("A unique identifier for the processed payment mandate."), + timestamp: z + .string() + .describe("The date and time the payment receipt was created, in ISO 8601 format."), + paymentId: z + .string() + .describe("A unique identifier for the payment."), + amount: z.object({ + currency: z.string().describe("The three-letter ISO 4217 currency code."), + value: z.number().describe("The monetary value."), + }).describe("The monetary amount of the payment."), + paymentStatus: paymentStatusSchema + .describe("The status of the payment."), + paymentMethodDetails: z + .record(z.string(), z.unknown()) + .optional() + .describe("The payment method used for the transaction."), +}); diff --git a/samples/typescript/src/common/schemas/shipping-address.ts b/samples/typescript/src/common/schemas/shipping-address.ts new file mode 100644 index 00000000..65ba23ed --- /dev/null +++ b/samples/typescript/src/common/schemas/shipping-address.ts @@ -0,0 +1,30 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from "zod"; + +export const shippingAddressSchema = z.object({ + city: z.string().optional(), + country: z.string().optional(), + dependent_locality: z.string().optional(), + organization: z.string().optional(), + phone_number: z.string().optional(), + postal_code: z.string().optional(), + recipient: z.string().optional(), + region: z.string().optional(), + sorting_code: z.string().optional(), + address_line: z.array(z.string()).optional(), +}); diff --git a/samples/typescript/src/common/server/a2a-context.ts b/samples/typescript/src/common/server/a2a-context.ts new file mode 100644 index 00000000..dd0a9bdf --- /dev/null +++ b/samples/typescript/src/common/server/a2a-context.ts @@ -0,0 +1,93 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Task } from '@a2a-js/sdk'; +import type { ExecutionEventBus } from '@a2a-js/sdk/server'; + +/** + * Per-request A2A context that must bypass ADK's session cloneDeep. + * + * ADK's InMemorySessionService deep-clones session state on every + * getSession/createSession call, which breaks live object references + * (eventBus methods, task object identity). This module-level Map + * provides a side-channel for tools to access these live objects. + */ +export interface A2AContext { + dataParts: Record[]; + eventBus: ExecutionEventBus; + currentTask: Task; +} + +const contextStore = new Map(); + +/** Store A2A context for a session. Called by executor before runner.runAsync. */ +export function setA2AContext(sessionId: string, ctx: A2AContext): void { + contextStore.set(sessionId, ctx); +} + +/** Retrieve A2A context inside a tool. Uses the session ID from tool context. */ +export function getA2AContext(sessionId: string): A2AContext | undefined { + return contextStore.get(sessionId); +} + +/** No-op event bus for ADK dev mode (npm run dev) where no A2A server is running. */ +const noopEventBus: ExecutionEventBus = { + publish: () => {}, + finished: () => {}, +} as unknown as ExecutionEventBus; + +/** + * Retrieve A2A context from a tool's execution context. + * This is the primary API for tools to access dataParts, eventBus, and currentTask. + * + * When running in ADK dev mode (npm run dev), no BaseAgentExecutor is active, + * so no A2A context exists. In that case, returns a fallback context with + * empty dataParts and a no-op eventBus so tools degrade gracefully. + * + * @param toolContext - The tool's execution context (second arg of FunctionTool.execute) + * @returns The A2A context with dataParts, eventBus, and currentTask + */ +export function getA2AContextFromTool(toolContext: { + invocationContext: { session: { id: string } }; +}): A2AContext { + const sessionId = toolContext.invocationContext.session.id; + const ctx = contextStore.get(sessionId); + if (!ctx) { + // ADK dev mode fallback: no A2A server running, return safe defaults + console.warn( + `[A2A] No context for session ${sessionId} — running in ADK dev mode. ` + + 'Using fallback (empty dataParts, no-op eventBus).' + ); + return { + dataParts: [], + eventBus: noopEventBus, + currentTask: { + kind: 'task', + id: sessionId, + contextId: sessionId, + status: { state: 'working', timestamp: new Date().toISOString() }, + history: [], + artifacts: [], + } as Task, + }; + } + return ctx; +} + +/** Clean up after request completes. Called by executor after runner finishes. */ +export function clearA2AContext(sessionId: string): void { + contextStore.delete(sessionId); +} diff --git a/samples/typescript/src/common/server/base-executor.ts b/samples/typescript/src/common/server/base-executor.ts new file mode 100644 index 00000000..7b480feb --- /dev/null +++ b/samples/typescript/src/common/server/base-executor.ts @@ -0,0 +1,464 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { v4 as uuidv4 } from 'uuid'; +import type { Runner } from '@google/adk'; +import type { + DataPart, + Message, + Task, + TaskStatusUpdateEvent, + TextPart, +} from '@a2a-js/sdk'; +import type { + AgentExecutor, + RequestContext, + ExecutionEventBus, +} from '@a2a-js/sdk/server'; +import { sessionService } from '../config/session.js'; +import { setA2AContext, clearA2AContext } from './a2a-context.js'; + +interface ADKEvent { + author: string; + content?: { + parts?: Array<{ + text?: string; + thought?: boolean; + functionCall?: { name: string; args: unknown; id: string }; + functionResponse?: { name: string; response: unknown; id: string }; + }>; + role?: string; + }; + finishReason?: string; + usageMetadata?: unknown; +} + +interface BaseExecutorOptions { + agentName: string; + appName: string; + runner: Runner; + workingMessage: string; + /** Maximum number of LLM calls per request. Defaults to 10. */ + maxLlmCalls?: number; + /** Called before ADK agent runs. Return modified message text or undefined to use original. */ + preprocessMessage?: (params: { + userText: string; + dataParts: Record[]; + existingTask: Task | undefined; + session: { state: Record }; + }) => string | undefined; + /** Called after ADK event loop. Return to override default completion behavior. */ + postprocessResult?: (params: { + responseText: string; + toolWasCalled: boolean; + lastToolResult: Record | undefined; + session: { state: Record }; + eventBus: ExecutionEventBus; + taskId: string; + contextId: string; + }) => TaskStatusUpdateEvent | null | undefined; + /** Called before session creation to inject extra state. */ + initSessionState?: (params: { + dataParts: Record[]; + session: { state: Record }; + }) => void; + /** + * Called after session init but before ADK runner executes. + * Return a TaskStatusUpdateEvent to short-circuit execution (e.g., when a + * previous result is cached). Return undefined to proceed normally. + */ + shouldShortCircuit?: (params: { + session: { state: Record }; + dataParts: Record[]; + eventBus: ExecutionEventBus; + taskId: string; + contextId: string; + }) => TaskStatusUpdateEvent | undefined; +} + +/** + * Base executor that implements the common A2A agent execution pattern. + * Reduces ~200 lines of boilerplate per server to ~20 lines of config. + */ +export class BaseAgentExecutor implements AgentExecutor { + private cancelledTasks = new Map(); + private static readonly CANCELLED_TASK_TTL_MS = 5 * 60 * 1000; // 5 minutes + private options: BaseExecutorOptions; + + constructor(options: BaseExecutorOptions) { + this.options = options; + // Periodic cleanup of expired cancellation entries + setInterval(() => { + const now = Date.now(); + for (const [taskId, timestamp] of this.cancelledTasks) { + if (now - timestamp > BaseAgentExecutor.CANCELLED_TASK_TTL_MS) { + this.cancelledTasks.delete(taskId); + } + } + }, 60_000).unref(); + } + + public cancelTask = async (taskId: string, eventBus: ExecutionEventBus) => { + this.cancelledTasks.set(taskId, Date.now()); + eventBus.publish({ + kind: 'status-update', + taskId, + contextId: '', + status: { + state: 'canceled', + timestamp: new Date().toISOString(), + }, + final: true, + } satisfies TaskStatusUpdateEvent); + eventBus.finished(); + }; + + private isTaskCancelled(taskId: string): boolean { + return this.cancelledTasks.has(taskId); + } + + async execute(requestContext: RequestContext, eventBus: ExecutionEventBus) { + const { agentName, appName, runner, workingMessage } = this.options; + const userMessage = requestContext.userMessage; + const existingTask = requestContext.task; + + const taskId = existingTask?.id || uuidv4(); + const contextId = + userMessage.contextId || existingTask?.contextId || uuidv4(); + + // Publish initial task if new + if (!existingTask) { + const initialTask: Task = { + kind: 'task', + id: taskId, + contextId, + status: { + state: 'submitted', + timestamp: new Date().toISOString(), + }, + history: [userMessage], + metadata: userMessage.metadata, + artifacts: [], + }; + eventBus.publish(initialTask); + } + + // Publish working status + eventBus.publish({ + kind: 'status-update', + taskId, + contextId, + status: { + state: 'working', + message: { + kind: 'message', + role: 'agent', + messageId: uuidv4(), + parts: [{ kind: 'text', text: workingMessage }], + taskId, + contextId, + }, + timestamp: new Date().toISOString(), + }, + final: false, + } satisfies TaskStatusUpdateEvent); + + // Extract text from message + const userText = userMessage.parts + .filter((p): p is TextPart => p.kind === 'text' && !!(p as TextPart).text) + .map((p) => p.text) + .join('\n'); + + if (!userText) { + eventBus.publish({ + kind: 'status-update', + taskId, + contextId, + status: { + state: 'failed', + message: { + kind: 'message', + role: 'agent', + messageId: uuidv4(), + parts: [{ kind: 'text', text: 'No input message found to process.' }], + taskId, + contextId, + }, + timestamp: new Date().toISOString(), + }, + final: true, + } satisfies TaskStatusUpdateEvent); + eventBus.finished(); + return; + } + + // Extract dataParts from A2A message + const dataParts = userMessage.parts + .filter((p): p is DataPart => p.kind === 'data') + .map((p) => p.data); + + const currentTask: Task = existingTask || { + kind: 'task', + id: taskId, + contextId, + status: { + state: 'working', + timestamp: new Date().toISOString(), + }, + history: [userMessage], + artifacts: [], + }; + + try { + // Get or create ADK session + let session = await sessionService.getSession({ + appName, + userId: 'user', + sessionId: contextId, + }); + + if (!session) { + session = await sessionService.createSession({ + appName, + userId: 'user', + sessionId: contextId, + }); + } + + // Allow subclass to inject extra session state + if (this.options.initSessionState) { + this.options.initSessionState({ dataParts, session }); + } + + // Allow subclass to short-circuit before running the ADK agent + if (this.options.shouldShortCircuit) { + const shortCircuit = this.options.shouldShortCircuit({ + session, + dataParts, + eventBus, + taskId, + contextId, + }); + if (shortCircuit) { + eventBus.publish(shortCircuit); + eventBus.finished(); + return; + } + } + + // Store A2A context in a side-channel that bypasses ADK's cloneDeep. + // ADK's InMemorySessionService deep-clones session state on every + // getSession() call, which breaks live object references (eventBus, + // currentTask). The side-channel Map lets tools look up these objects + // by session ID without going through cloneDeep. + setA2AContext(session.id, { dataParts, eventBus, currentTask }); + + try { + // Allow subclass to preprocess the message + let messageText = this.options.preprocessMessage?.({ + userText, + dataParts, + existingTask, + session, + }) ?? userText; + + // Append a structured summary of A2A data parts so the LLM knows + // what data is available and can decide which tool to call. Tools + // access the full objects via the side-channel, but the LLM needs + // visibility into available keys and values to make correct decisions. + if (dataParts.length > 0) { + const dataPartSummaries = dataParts.map((dp) => { + const entries = Object.entries(dp).map(([key, value]) => { + // For large objects, show type + top-level keys; for scalars show the value + if (value && typeof value === 'object') { + const topKeys = Object.keys(value as Record).slice(0, 5); + return ` ${key}: {${topKeys.join(', ')}${topKeys.length < Object.keys(value as Record).length ? ', ...' : ''}}`; + } + return ` ${key}: ${JSON.stringify(value)}`; + }); + return entries.join('\n'); + }); + messageText += `\n\n[Available data parts — call the appropriate tool to process them, do NOT ask the user for this data]:\n${dataPartSummaries.join('\n')}`; + } + + // Run ADK agent with a bounded number of LLM calls to prevent infinite loops + const events = runner.runAsync({ + userId: 'user', + sessionId: session.id, + newMessage: { + role: 'user', + parts: [{ text: messageText }], + }, + runConfig: { + maxLlmCalls: this.options.maxLlmCalls ?? 10, + }, + }); + + // Collect response and stream intermediate status updates + let responseText = ''; + let toolWasCalled = false; + let lastToolResult: Record | undefined; + let agentEventCount = 0; + + for await (const event of events) { + // Check for cancellation during execution + if (this.isTaskCancelled(taskId)) { + eventBus.finished(); + return; + } + + const adkEvent = event as unknown as ADKEvent; + // Count and extract content from any agent-authored event (root or + // sub_agent). Sub_agents handle most user-facing replies in + // multi-agent flows, so restricting to the root agent loses content. + if (adkEvent.author && adkEvent.author !== 'user') { + agentEventCount++; + const parts = adkEvent.content?.parts; + if (parts && Array.isArray(parts)) { + // Extract text responses (skip internal thought parts) + const textContent = parts.find((c) => c.text && !c.thought); + if (textContent?.text) { + responseText = textContent.text; + } + // Detect function calls + if (parts.some((c) => c.functionCall)) { + toolWasCalled = true; + } + // Extract function response results + const funcResponse = parts.find((c) => c.functionResponse); + if (funcResponse?.functionResponse?.response) { + lastToolResult = funcResponse.functionResponse.response as Record; + } + } + } + } + + // Detect silent LLM failures: no agent events means the model + // likely returned an error that the ADK swallowed + if (agentEventCount === 0) { + console.error(`[${agentName}] No events received from ADK — possible LLM API error`); + eventBus.publish({ + kind: 'status-update', + taskId, + contextId, + status: { + state: 'failed', + message: { + kind: 'message', + role: 'agent', + messageId: uuidv4(), + parts: [{ kind: 'text', text: 'Agent failed: no response from LLM. Check API key and model configuration.' }], + taskId, + contextId, + }, + timestamp: new Date().toISOString(), + }, + final: true, + } satisfies TaskStatusUpdateEvent); + eventBus.finished(); + return; + } + + // Re-fetch session to pick up state changes made by tools during + // the runner execution. ADK's InMemorySessionService deep-clones on + // getSession(), so the `session` variable from before the run is stale. + const updatedSession = await sessionService.getSession({ + appName, + userId: 'user', + sessionId: contextId, + }); + + // Allow subclass to handle special post-processing + if (this.options.postprocessResult) { + const override = this.options.postprocessResult({ + responseText, + toolWasCalled, + lastToolResult, + session: updatedSession || session, + eventBus, + taskId, + contextId, + }); + if (override) { + eventBus.publish(override); + eventBus.finished(); + return; + } + // If postprocessResult returns null, it handled publishing itself + if (override === null) { + eventBus.finished(); + return; + } + } + + // Default: publish completion + const finalText = responseText || + (toolWasCalled ? 'Request processed successfully.' : 'Completed.'); + + const agentMessage: Message = { + kind: 'message', + role: 'agent', + messageId: uuidv4(), + parts: [{ kind: 'text', text: finalText }], + taskId, + contextId, + }; + + eventBus.publish({ + kind: 'status-update', + taskId, + contextId, + status: { + state: 'completed', + message: agentMessage, + timestamp: new Date().toISOString(), + }, + final: true, + } satisfies TaskStatusUpdateEvent); + eventBus.finished(); + + } finally { + clearA2AContext(session.id); + } + + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred'; + + console.error(`[${agentName}] Error:`, errorMessage); + + eventBus.publish({ + kind: 'status-update', + taskId, + contextId, + status: { + state: 'failed', + message: { + kind: 'message', + role: 'agent', + messageId: uuidv4(), + parts: [{ kind: 'text', text: `Agent error: ${errorMessage}` }], + taskId, + contextId, + }, + timestamp: new Date().toISOString(), + }, + final: true, + } satisfies TaskStatusUpdateEvent); + eventBus.finished(); + } + } +} diff --git a/samples/typescript/src/common/server/bootstrap.ts b/samples/typescript/src/common/server/bootstrap.ts new file mode 100644 index 00000000..89ab5de6 --- /dev/null +++ b/samples/typescript/src/common/server/bootstrap.ts @@ -0,0 +1,96 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import express from 'express'; +import type { Server } from 'http'; +import type { AgentCard } from '@a2a-js/sdk'; +import { + InMemoryTaskStore, + type AgentExecutor, + DefaultRequestHandler, +} from '@a2a-js/sdk/server'; +import { A2AExpressApp } from '@a2a-js/sdk/server/express'; +import { createRateLimiter, validateA2ARequest } from './middleware.js'; + +interface BootstrapOptions { + agentCard: AgentCard; + agentExecutor: AgentExecutor; + port: number; + label: string; +} + +/** + * Bootstraps an A2A Express server with rate limiting and validation. + * Returns the HTTP server handle for graceful shutdown and testing. + */ +export function bootstrapServer(options: BootstrapOptions): Server { + const { agentCard, agentExecutor, port, label } = options; + + const taskStore = new InMemoryTaskStore(); + const requestHandler = new DefaultRequestHandler( + agentCard, + taskStore, + agentExecutor + ); + + const appBuilder = new A2AExpressApp(requestHandler); + const app = express(); + + // Parse JSON bodies before validation middleware can inspect them. + // A JSON error handler is required here because the SDK adds its own + // express.json() + jsonErrorHandler on the router, but this app-level + // parser runs first. Without a handler, a SyntaxError from malformed + // JSON (e.g. bad escape sequences) propagates to Express's default + // error handler which crashes the agent process. + app.use(express.json()); + app.use((err: Error, _req: express.Request, res: express.Response, next: express.NextFunction) => { + if (err instanceof SyntaxError && 'body' in err) { + res.status(400).json({ + jsonrpc: '2.0', + id: null, + error: { + code: -32700, + message: 'Parse error: invalid JSON in request body.', + }, + }); + return; + } + next(err); + }); + + // Apply middleware before A2A routes + app.use(createRateLimiter({ windowMs: 60_000, maxRequests: 60 })); + app.use(validateA2ARequest); + + // Health check endpoint + app.get('/health', (_req: express.Request, res: express.Response) => { + res.json({ status: 'ok', agent: agentCard.name, timestamp: new Date().toISOString() }); + }); + + const expressApp = appBuilder.setupRoutes(app); + + const PORT = process.env.PORT || port; + const server = expressApp.listen(PORT, () => { + console.log(`[${label}] Running on http://localhost:${PORT}`); + }); + + // Workaround: keep the event loop alive on Node v23+ with tsx where + // ADK agent imports can cause the server's TCP handle to be unref'd + const keepAlive = setInterval(() => {}, 1 << 30); + server.on('close', () => clearInterval(keepAlive)); + + return server; +} diff --git a/samples/typescript/src/common/server/middleware.ts b/samples/typescript/src/common/server/middleware.ts new file mode 100644 index 00000000..a4846db7 --- /dev/null +++ b/samples/typescript/src/common/server/middleware.ts @@ -0,0 +1,121 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Request, Response, NextFunction } from 'express'; + +/** + * Simple in-memory rate limiter for A2A endpoints. + * Tracks request counts per IP within a fixed window. + */ +export function createRateLimiter(options: { + windowMs: number; + maxRequests: number; +}) { + const { windowMs, maxRequests } = options; + const requestCounts = new Map(); + + // Periodic cleanup of expired entries + const cleanupTimer = setInterval(() => { + const now = Date.now(); + for (const [key, entry] of requestCounts) { + if (now > entry.resetTime) { + requestCounts.delete(key); + } + } + }, windowMs); + cleanupTimer.unref(); + + return (req: Request, res: Response, next: NextFunction): void => { + const clientIp = req.ip || req.socket.remoteAddress || 'unknown'; + const now = Date.now(); + + const entry = requestCounts.get(clientIp); + + if (!entry || now > entry.resetTime) { + requestCounts.set(clientIp, { count: 1, resetTime: now + windowMs }); + next(); + return; + } + + if (entry.count >= maxRequests) { + const requestId = req.body?.id ?? null; + res.status(429).json({ + jsonrpc: '2.0', + id: requestId, + error: { + code: -32000, + message: 'Too many requests. Please try again later.', + }, + }); + return; + } + + entry.count++; + next(); + }; +} + +/** + * Validates that incoming JSON-RPC requests have the required A2A structure. + */ +export function validateA2ARequest(req: Request, res: Response, next: NextFunction): void { + // Only validate POST requests to the A2A endpoint + if (req.method !== 'POST' || !req.path.endsWith('/')) { + next(); + return; + } + + const body = req.body; + const requestId = body?.id ?? null; + + if (!body || typeof body !== 'object') { + res.status(400).json({ + jsonrpc: '2.0', + id: null, + error: { + code: -32600, + message: 'Invalid request: body must be a JSON object.', + }, + }); + return; + } + + if (body.jsonrpc !== '2.0') { + res.status(400).json({ + jsonrpc: '2.0', + id: requestId, + error: { + code: -32600, + message: 'Invalid request: jsonrpc must be "2.0".', + }, + }); + return; + } + + if (!body.method || typeof body.method !== 'string') { + res.status(400).json({ + jsonrpc: '2.0', + id: requestId, + error: { + code: -32600, + message: 'Invalid request: method is required.', + }, + }); + return; + } + + next(); +} diff --git a/samples/typescript/src/common/types/cart-mandate.ts b/samples/typescript/src/common/types/cart-mandate.ts new file mode 100644 index 00000000..e4f84662 --- /dev/null +++ b/samples/typescript/src/common/types/cart-mandate.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { cartMandateSchema } from "../schemas/cart-mandate.js"; +import type { z } from "zod"; + +export type CartMandate = z.infer; diff --git a/samples/typescript/src/common/types/intent-mandate.ts b/samples/typescript/src/common/types/intent-mandate.ts new file mode 100644 index 00000000..a30f3695 --- /dev/null +++ b/samples/typescript/src/common/types/intent-mandate.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { intentMandateSchema } from "../schemas/intent-mandate.js"; +import type { z } from "zod"; + +export type IntentMandate = z.infer; diff --git a/samples/typescript/src/common/types/payment-item.ts b/samples/typescript/src/common/types/payment-item.ts new file mode 100644 index 00000000..f51d90e2 --- /dev/null +++ b/samples/typescript/src/common/types/payment-item.ts @@ -0,0 +1,27 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type PaymentCurrencyAmount = { + currency: string; + value: number; +}; + +export type PaymentItem = { + label: string; + amount: PaymentCurrencyAmount; + pending?: boolean; + refundPeriod: number; +}; diff --git a/samples/typescript/src/common/types/payment-mandate.ts b/samples/typescript/src/common/types/payment-mandate.ts new file mode 100644 index 00000000..35d21114 --- /dev/null +++ b/samples/typescript/src/common/types/payment-mandate.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { paymentMandateSchema } from "../schemas/payment-mandate.js"; +import type { z } from "zod"; + +export type PaymentMandate = z.infer; diff --git a/samples/typescript/src/common/types/payment-receipt.ts b/samples/typescript/src/common/types/payment-receipt.ts new file mode 100644 index 00000000..570685da --- /dev/null +++ b/samples/typescript/src/common/types/payment-receipt.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { paymentReceiptSchema } from "../schemas/payment-receipt.js"; +import type { z } from "zod"; + +export type PaymentReceipt = z.infer; diff --git a/samples/typescript/src/common/utils/artifact.ts b/samples/typescript/src/common/utils/artifact.ts new file mode 100644 index 00000000..8ed1becb --- /dev/null +++ b/samples/typescript/src/common/utils/artifact.ts @@ -0,0 +1,70 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Artifact, DataPart } from "@a2a-js/sdk"; + +/** + * Extracts and validates canonical objects from A2A artifacts by data key. + * + * Searches through artifact parts for DataParts that contain the specified + * key, then validates each match with the provided schema. + * + * @param artifacts - The A2A artifacts to search + * @param dataKey - The data key to look for (e.g., "ap2.mandates.CartMandate") + * @param schema - A Zod-style schema with a `parse` method for validation + * @returns An array of validated objects + */ +export function findCanonicalObjects( + artifacts: Artifact[], + dataKey: string, + schema: { parse: (data: unknown) => T } +): T[] { + const canonicalObjects: T[] = []; + + for (const artifact of artifacts) { + for (const part of artifact.parts) { + if (part.kind === "data") { + const data = (part as DataPart).data as Record; + if (data[dataKey]) { + try { + const validatedObject = schema.parse(data[dataKey]); + canonicalObjects.push(validatedObject); + } catch (error) { + console.warn(`Failed to validate object for key ${dataKey}:`, error); + } + } + } + } + } + + return canonicalObjects; +} + +/** + * Returns the data from the first DataPart found across all artifacts. + */ +export const getFirstDataPart = ( + artifacts: Artifact[] +): Record => { + for (const artifact of artifacts) { + for (const part of artifact.parts) { + if (part.kind === "data") { + return (part as DataPart).data as Record; + } + } + } + return {}; +}; diff --git a/samples/typescript/src/common/utils/message.ts b/samples/typescript/src/common/utils/message.ts new file mode 100644 index 00000000..21ada712 --- /dev/null +++ b/samples/typescript/src/common/utils/message.ts @@ -0,0 +1,83 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Finds and returns the value for the first occurrence of the key in the data parts. + * + * @param dataKey - The key to search for. + * @param dataParts - The data parts to be searched (array of objects with data). + * @returns The value for the first occurrence of the key, or null if not found. + */ +export const findDataPart = ( + dataKey: string, + dataParts: Record[] +): unknown => { + for (const dataPart of dataParts) { + if (dataKey in dataPart) { + return dataPart[dataKey]; + } + } + return null; +}; + +/** + * Finds and returns all values for the given key in the data parts. + * + * @param dataKey - The key to search for. + * @param dataParts - The data parts to be searched (array of objects with data). + * @returns An array of all values for the given key. + */ +export const findDataParts = ( + dataKey: string, + dataParts: Record[] +): unknown[] => { + const dataPartsWithKey: unknown[] = []; + for (const dataPart of dataParts) { + if (dataKey in dataPart) { + dataPartsWithKey.push(dataPart[dataKey]); + } + } + return dataPartsWithKey; +}; + +/** + * Converts the data part value for the given key to a canonical object using a Zod schema. + * This is the TypeScript equivalent of Python's parse_canonical_object. + * + * @param dataKey - The key to search for. + * @param dataParts - The data parts to be searched (array of objects with data). + * @param schema - The Zod schema to validate and parse the data. + * @returns The canonical object created from the data part value. + * @throws Error if the data key is not found or validation fails. + * + * @example + * const paymentMandate = parseCanonicalObject( + * "ap2.mandates.PaymentMandate", + * dataParts, + * paymentMandateSchema + * ); + */ +export const parseCanonicalObject = ( + dataKey: string, + dataParts: Record[], + schema: { parse: (data: unknown) => T } +): T => { + const canonicalObjectData = findDataPart(dataKey, dataParts); + if (!canonicalObjectData) { + throw new Error(`${dataKey} not found in data parts.`); + } + return schema.parse(canonicalObjectData); +}; diff --git a/samples/typescript/src/common/vc/ap2-context.ts b/samples/typescript/src/common/vc/ap2-context.ts new file mode 100644 index 00000000..902d43b0 --- /dev/null +++ b/samples/typescript/src/common/vc/ap2-context.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Custom JSON-LD context for AP2 payment credential terms. + * + * This defines the vocabulary for payment method properties used in + * PaymentCredential VCs so that JSON-LD expansion/canonicalization + * can handle them without "safe mode" validation errors. + */ + +export const AP2_CONTEXT_URL = 'https://ap2.example/contexts/payment/v1'; + +/** + * The AP2 payment context document. Maps AP2-specific terms to a + * local vocabulary namespace so they are recognized by JSON-LD processors. + */ +export const ap2PaymentContext = { + '@context': { + '@vocab': 'https://ap2.example/vocab#', + PaymentCredential: 'https://ap2.example/vocab#PaymentCredential', + PaymentMethod: 'https://ap2.example/vocab#PaymentMethod', + alias: 'https://ap2.example/vocab#alias', + network: 'https://ap2.example/vocab#network', + cryptogram: 'https://ap2.example/vocab#cryptogram', + token: 'https://ap2.example/vocab#token', + card_holder_name: 'https://ap2.example/vocab#card_holder_name', + card_expiration: 'https://ap2.example/vocab#card_expiration', + card_billing_address: 'https://ap2.example/vocab#card_billing_address', + account_number: 'https://ap2.example/vocab#account_number', + brand: 'https://ap2.example/vocab#brand', + account_identifier: 'https://ap2.example/vocab#account_identifier', + postal_code: 'https://ap2.example/vocab#postal_code', + country: 'https://ap2.example/vocab#country', + paymentMandateId: 'https://ap2.example/vocab#paymentMandateId', + }, +}; diff --git a/samples/typescript/src/common/vc/credentials.ts b/samples/typescript/src/common/vc/credentials.ts new file mode 100644 index 00000000..f8da061d --- /dev/null +++ b/samples/typescript/src/common/vc/credentials.ts @@ -0,0 +1,183 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * W3C Verifiable Credential issuance and verification utilities. + * + * Uses @digitalbazaar/vc with Ed25519Signature2020 to issue and verify + * payment credential VCs within the AP2 multi-agent system. + */ +import { v4 as uuidv4 } from 'uuid'; +// @ts-expect-error - JS-only module without type declarations +import * as vc from '@digitalbazaar/vc'; +// @ts-expect-error - JS-only module without type declarations +import { Ed25519Signature2020 } from '@digitalbazaar/ed25519-signature-2020'; +import { getKeyManager } from './key-manager.js'; +import { documentLoader } from './document-loader.js'; +import { AP2_CONTEXT_URL } from './ap2-context.js'; +import type { + VerifiableCredential, + VerifiablePresentation, + VerifyCredentialResult, + VerifyPresentationResult, +} from './types.js'; + +const CREDENTIALS_CONTEXT_V1 = 'https://www.w3.org/2018/credentials/v1'; +const ED25519_2020_CONTEXT = 'https://w3id.org/security/suites/ed25519-2020/v1'; + +/** + * Build a signing suite from the current key manager state. + */ +function buildSigningSuite(): InstanceType { + const { keyPair } = getKeyManager(); + return new Ed25519Signature2020({ key: keyPair }); +} + +/** + * Build a verification suite (no private key needed). + */ +function buildVerificationSuite(): InstanceType { + return new Ed25519Signature2020(); +} + +// -- Credential Issuance -- + +export interface IssuePaymentCredentialOptions { + /** The subject holding the credential (e.g., user email or DID). */ + subjectId?: string; + /** The payment method data to embed in the credential. */ + paymentMethod: Record; + /** The payment mandate ID this credential is bound to. */ + paymentMandateId?: string; +} + +/** + * Issue a W3C Verifiable Credential for a payment method. + * + * The credential embeds the payment method data as the credentialSubject + * and is signed by the credentials-provider's Ed25519 key. + * + * @returns A signed VerifiableCredential object. + */ +export async function issuePaymentCredential( + options: IssuePaymentCredentialOptions +): Promise { + const { paymentMethod, subjectId, paymentMandateId } = options; + const { issuerId } = getKeyManager(); + const suite = buildSigningSuite(); + + const credential: Record = { + '@context': [CREDENTIALS_CONTEXT_V1, ED25519_2020_CONTEXT, AP2_CONTEXT_URL], + id: `urn:uuid:${uuidv4()}`, + type: ['VerifiableCredential', 'PaymentCredential'], + issuer: issuerId, + issuanceDate: new Date().toISOString(), + credentialSubject: { + ...(subjectId ? { id: subjectId } : {}), + type: 'PaymentMethod', + ...paymentMethod, + ...(paymentMandateId ? { paymentMandateId } : {}), + }, + }; + + const signedCredential: VerifiableCredential = await vc.issue({ + credential, + suite, + documentLoader, + }); + + return signedCredential; +} + +// -- Credential Verification -- + +/** + * Verify a W3C Verifiable Credential. + * + * Checks the cryptographic signature and validates the credential structure. + * + * @returns The verification result with `verified: true/false`. + */ +export async function verifyCredential( + credential: VerifiableCredential +): Promise { + const suite = buildVerificationSuite(); + + const result: VerifyCredentialResult = await vc.verifyCredential({ + credential, + suite, + documentLoader, + }); + + return result; +} + +/** + * Verify a credential and extract the credentialSubject if valid. + * + * @returns The credential subject data, or throws if verification fails. + */ +export async function verifyAndExtractSubject( + credential: VerifiableCredential +): Promise> { + const result = await verifyCredential(credential); + + if (!result.verified) { + const errorMsg = result.error?.message ?? 'Unknown verification error'; + throw new Error(`Credential verification failed: ${errorMsg}`); + } + + const subject = credential.credentialSubject; + if (Array.isArray(subject)) { + return subject[0] as Record; + } + return subject as Record; +} + +// -- Verifiable Presentations -- + +/** + * Create an unsigned Verifiable Presentation wrapping one or more VCs. + * + * Unsigned presentations are sufficient for the AP2 inter-agent flow + * because the A2A transport layer provides message authenticity. + */ +export function createPresentation( + credentials: VerifiableCredential | VerifiableCredential[], + holder?: string +): VerifiablePresentation { + return vc.createPresentation({ + verifiableCredential: credentials, + holder, + version: 1.0, + }); +} + +/** + * Verify an unsigned Verifiable Presentation and all contained credentials. + */ +export async function verifyPresentation( + presentation: VerifiablePresentation +): Promise { + const suite = buildVerificationSuite(); + + return vc.verify({ + presentation, + suite, + unsignedPresentation: true, + documentLoader, + }); +} diff --git a/samples/typescript/src/common/vc/document-loader.ts b/samples/typescript/src/common/vc/document-loader.ts new file mode 100644 index 00000000..2ac2e3b8 --- /dev/null +++ b/samples/typescript/src/common/vc/document-loader.ts @@ -0,0 +1,118 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Custom document loader for VC operations. + * + * Combines the built-in credential/security contexts from the digitalbazaar + * packages with a local store for issuer controller documents and + * verification method resolution. This avoids any network fetches. + */ +import type { DocumentLoader, DocumentLoaderResult } from './types.js'; + +// @ts-expect-error - JS-only module without type declarations +import { contexts as credentialContexts } from '@digitalbazaar/credentials-context'; +// @ts-expect-error - JS-only module without type declarations +import { contexts as securityContexts } from '@digitalbazaar/security-context'; +// @ts-expect-error - JS-only module without type declarations +import ed25519Context from 'ed25519-signature-2020-context'; +import { AP2_CONTEXT_URL, ap2PaymentContext } from './ap2-context.js'; + +/** + * In-memory store for dynamically registered documents (controller docs, + * verification methods). Populated by the key manager when keys are created. + */ +const localDocuments = new Map(); + +/** Register a document so the loader can resolve it by URL. */ +export function registerDocument(url: string, document: unknown): void { + localDocuments.set(url, document); +} + +/** Remove a registered document. */ +export function unregisterDocument(url: string): void { + localDocuments.delete(url); +} + +/** Clear all registered documents (useful for testing). */ +export function clearDocuments(): void { + localDocuments.clear(); +} + +/** + * Build a static context map from the bundled packages. + */ +function buildStaticContexts(): Map { + const contexts = new Map(); + + // W3C Credentials contexts (v1, v2) + for (const [url, doc] of credentialContexts as Map) { + contexts.set(url, doc); + } + + // Security contexts (v1, v2) + for (const [url, doc] of securityContexts as Map) { + contexts.set(url, doc); + } + + // Ed25519 2020 suite context + for (const [url, doc] of ed25519Context.contexts as Map) { + contexts.set(url, doc); + } + + // AP2 custom payment context + contexts.set(AP2_CONTEXT_URL, ap2PaymentContext); + + return contexts; +} + +const staticContexts = buildStaticContexts(); + +/** + * Document loader that resolves: + * 1. Static W3C/security/Ed25519 contexts from bundled packages + * 2. Dynamically registered local documents (controller docs, keys) + * + * Never makes network requests. + */ +export const documentLoader: DocumentLoader = async ( + url: string +): Promise => { + // Check static contexts first + const staticDoc = staticContexts.get(url); + if (staticDoc !== undefined) { + return { + contextUrl: null, + documentUrl: url, + document: staticDoc, + }; + } + + // Check dynamically registered documents + const localDoc = localDocuments.get(url); + if (localDoc !== undefined) { + return { + contextUrl: null, + documentUrl: url, + document: localDoc, + }; + } + + throw new Error( + `Document loader unable to load URL "${url}". ` + + 'Ensure the document is registered or a known context.' + ); +}; diff --git a/samples/typescript/src/common/vc/index.ts b/samples/typescript/src/common/vc/index.ts new file mode 100644 index 00000000..d3855089 --- /dev/null +++ b/samples/typescript/src/common/vc/index.ts @@ -0,0 +1,65 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * W3C Verifiable Credentials module for the AP2 multi-agent system. + * + * Provides VC issuance, verification, and presentation creation + * using Ed25519Signature2020 signatures. + * + * Usage: + * import { initKeyManager, issuePaymentCredential, verifyCredential } from '../vc/index.js'; + * + * // At startup: + * await initKeyManager(); + * + * // Issue a credential: + * const vc = await issuePaymentCredential({ paymentMethod: { ... } }); + * + * // Verify a credential: + * const result = await verifyCredential(vc); + */ + +export { initKeyManager, getKeyManager, exportKeyPair, resetKeyManager } from './key-manager.js'; + +export { + issuePaymentCredential, + verifyCredential, + verifyAndExtractSubject, + createPresentation, + verifyPresentation, +} from './credentials.js'; + +export type { + IssuePaymentCredentialOptions, +} from './credentials.js'; + +export { + documentLoader, + registerDocument, + unregisterDocument, + clearDocuments, +} from './document-loader.js'; + +export { AP2_CONTEXT_URL } from './ap2-context.js'; + +export type { + VerifiableCredential, + VerifiablePresentation, + VerifyCredentialResult, + VerifyPresentationResult, + DocumentLoader, +} from './types.js'; diff --git a/samples/typescript/src/common/vc/key-manager.ts b/samples/typescript/src/common/vc/key-manager.ts new file mode 100644 index 00000000..93db9864 --- /dev/null +++ b/samples/typescript/src/common/vc/key-manager.ts @@ -0,0 +1,127 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Key pair management for VC operations. + * + * Generates Ed25519 key pairs and registers the corresponding controller + * documents and verification methods with the document loader so that + * issued credentials can be verified offline. + */ +// @ts-expect-error - JS-only module without type declarations +import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020'; +import type { Ed25519KeyPairInstance, Ed25519KeyPairExport } from './types.js'; +import { registerDocument } from './document-loader.js'; + +/** Default issuer DID-like identifier for the credentials provider. */ +const DEFAULT_ISSUER_ID = 'https://ap2.example/issuers/credentials-provider'; + +interface KeyManagerOptions { + /** The issuer identifier (URL or DID). */ + issuerId?: string; +} + +interface KeyManagerState { + keyPair: Ed25519KeyPairInstance; + issuerId: string; + verificationMethodId: string; +} + +let _state: KeyManagerState | null = null; + +/** + * Initialize the key manager: generate (or load) an Ed25519 key pair and + * register the controller document + verification method with the + * document loader. + * + * This should be called once at application startup. + */ +export async function initKeyManager( + options: KeyManagerOptions = {} +): Promise { + const issuerId = options.issuerId ?? DEFAULT_ISSUER_ID; + + // Generate a new key pair with the issuer as the controller + const keyPair: Ed25519KeyPairInstance = await Ed25519VerificationKey2020.generate({ + controller: issuerId, + }); + + // The key's id is auto-assigned as `${controller}#${keyFingerprint}` + const verificationMethodId = keyPair.id; + + // Register the controller document so the document loader can resolve it. + // This is what verifiers look up to find the public key. + // The controller document must use security/v2 context because it defines + // assertionMethod, authentication, and verificationMethod terms. + const controllerDoc = { + '@context': [ + 'https://w3id.org/security/v2', + 'https://w3id.org/security/suites/ed25519-2020/v1', + ], + id: issuerId, + assertionMethod: [verificationMethodId], + authentication: [verificationMethodId], + verificationMethod: [ + { + id: verificationMethodId, + type: 'Ed25519VerificationKey2020', + controller: issuerId, + publicKeyMultibase: keyPair.publicKeyMultibase, + }, + ], + }; + + registerDocument(issuerId, controllerDoc); + + // Also register the verification method URL directly so the signature + // verifier can dereference it by its full id. + registerDocument(verificationMethodId, { + '@context': 'https://w3id.org/security/suites/ed25519-2020/v1', + id: verificationMethodId, + type: 'Ed25519VerificationKey2020', + controller: issuerId, + publicKeyMultibase: keyPair.publicKeyMultibase, + }); + + _state = { keyPair, issuerId, verificationMethodId }; + return _state; +} + +/** Get the current key manager state. Throws if not initialized. */ +export function getKeyManager(): KeyManagerState { + if (!_state) { + throw new Error( + 'Key manager not initialized. Call initKeyManager() first.' + ); + } + return _state; +} + +/** Export the key pair (public key only by default). */ +export async function exportKeyPair( + options: { includePrivateKey?: boolean } = {} +): Promise { + const { keyPair } = getKeyManager(); + return keyPair.export({ + publicKey: true, + privateKey: options.includePrivateKey ?? false, + }); +} + +/** Reset the key manager (useful for testing). */ +export function resetKeyManager(): void { + _state = null; +} diff --git a/samples/typescript/src/common/vc/types.ts b/samples/typescript/src/common/vc/types.ts new file mode 100644 index 00000000..8c12ab57 --- /dev/null +++ b/samples/typescript/src/common/vc/types.ts @@ -0,0 +1,113 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * TypeScript type declarations for the digitalbazaar VC ecosystem. + * + * These libraries are JavaScript-only; these types provide a typed surface + * for the parts of the API we use. + */ + +// -- Ed25519 Key Pair -- + +export interface Ed25519KeyPairExport { + id: string; + type: 'Ed25519VerificationKey2020'; + controller: string; + publicKeyMultibase: string; + privateKeyMultibase?: string; +} + +export interface Ed25519KeyPairInstance { + id: string; + type: string; + controller: string; + publicKeyMultibase: string; + privateKeyMultibase?: string; + signer(): { sign(params: { data: Uint8Array }): Promise }; + verifier(): { verify(params: { data: Uint8Array; signature: Uint8Array }): Promise }; + export(options: { publicKey?: boolean; privateKey?: boolean }): Promise; +} + +// -- Signature Suite -- + +export interface Ed25519Signature2020Instance { + verificationMethod: string; +} + +// -- Verifiable Credential -- + +export interface VerifiableCredential { + '@context': string[]; + id?: string; + type: string[]; + issuer: string | { id: string; [key: string]: unknown }; + issuanceDate: string; + expirationDate?: string; + credentialSubject: CredentialSubject | CredentialSubject[]; + proof?: Proof; + [key: string]: unknown; +} + +export interface CredentialSubject { + id?: string; + [key: string]: unknown; +} + +export interface Proof { + type: string; + created: string; + verificationMethod: string; + proofPurpose: string; + proofValue: string; + [key: string]: unknown; +} + +// -- Verifiable Presentation -- + +export interface VerifiablePresentation { + '@context': string[]; + type: string[]; + verifiableCredential?: VerifiableCredential[]; + holder?: string; + id?: string; + proof?: Proof; +} + +// -- Verification Results -- + +export interface VerifyCredentialResult { + verified: boolean; + results?: Array<{ verified: boolean; error?: Error }>; + error?: Error; +} + +export interface VerifyPresentationResult { + verified: boolean; + presentationResult?: { verified: boolean; error?: Error }; + credentialResults?: VerifyCredentialResult[]; + error?: Error; +} + +// -- Document Loader -- + +export interface DocumentLoaderResult { + contextUrl: string | null; + documentUrl: string; + document: unknown; +} + +export type DocumentLoader = (url: string) => Promise; diff --git a/samples/typescript/src/roles/credentials-provider/account-manager.ts b/samples/typescript/src/roles/credentials-provider/account-manager.ts new file mode 100644 index 00000000..4967b602 --- /dev/null +++ b/samples/typescript/src/roles/credentials-provider/account-manager.ts @@ -0,0 +1,324 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * An in-memory manager of a user's 'account details'. + * + * Each 'account' contains a user's payment methods and shipping address. + * For demonstration purposes, several accounts are pre-populated with sample data. + * + * Token creation now issues W3C Verifiable Credentials (VCs) via + * @digitalbazaar/vc with Ed25519Signature2020 signatures. + */ +import { + issuePaymentCredential, + verifyAndExtractSubject, +} from '../../common/vc/index.js'; +import type { VerifiableCredential } from '../../common/vc/index.js'; + +export type PaymentMethod = { + type: string; + alias: string; + network?: { name?: string; formats?: string[] }[]; + cryptogram?: string; + token?: string; + card_holder_name?: string; + card_expiration?: string; + card_billing_address?: { country?: string; postal_code?: string }; + account_number?: string; + brand?: string; + account_identifier?: string; +}; + +type Account = { + shipping_address?: { + recipient: string; + organization?: string; + address_line: string[]; + city: string; + region: string; + postal_code: string; + country: string; + phone_number: string; + }; + payment_methods: { + [key: string]: PaymentMethod; + }; +}; + +const accountDb: { [email: string]: Account } = { + // Default demo account used when no user email has been collected. + "user@example.com": { + shipping_address: { + recipient: "Demo User", + address_line: ["123 Main St"], + city: "San Francisco", + region: "CA", + postal_code: "94105", + country: "US", + phone_number: "+14155551234", + }, + payment_methods: { + card1: { + type: "CARD", + alias: "Visa ending in 4242", + network: [{ name: "visa", formats: ["DPAN"] }], + cryptogram: "fake_cryptogram_demo", + token: "4242000000004242", + card_holder_name: "Demo User", + card_expiration: "12/2030", + card_billing_address: { + country: "US", + postal_code: "94105", + }, + }, + }, + }, + "bugsbunny@gmail.com": { + shipping_address: { + recipient: "Bugs Bunny", + organization: "Sample Organization", + address_line: ["123 Main St"], + city: "Sample City", + region: "ST", + postal_code: "00000", + country: "US", + phone_number: "+1-000-000-0000", + }, + payment_methods: { + card1: { + type: "CARD", + alias: "American Express ending in 4444", + network: [{ name: "amex", formats: ["DPAN"] }], + cryptogram: "fake_cryptogram_abc123", + token: "1111000000000000", + card_holder_name: "John Doe", + card_expiration: "12/2028", + card_billing_address: { + country: "US", + postal_code: "00000", + }, + }, + card2: { + type: "CARD", + alias: "American Express ending in 8888", + network: [{ name: "amex", formats: ["DPAN"] }], + cryptogram: "fake_cryptogram_ghi789", + token: "2222000000000000", + card_holder_name: "Bugs Bunny", + card_expiration: "10/2027", + card_billing_address: { + country: "US", + postal_code: "00000", + }, + }, + bank_account1: { + type: "BANK_ACCOUNT", + account_number: "111", + alias: "Primary bank account", + }, + digital_wallet1: { + type: "DIGITAL_WALLET", + brand: "PayPal", + account_identifier: "foo@bar.com", + alias: "Bugs's PayPal account", + }, + }, + }, + "daffyduck@gmail.com": { + payment_methods: { + bank_account1: { + type: "BANK_ACCOUNT", + brand: "Bank of Money", + account_number: "789", + alias: "Main checking account", + }, + }, + }, + "elmerfudd@gmail.com": { + payment_methods: { + digital_wallet1: { + type: "DIGITAL_WALLET", + brand: "PayPal", + account_identifier: "elmerfudd@gmail.com", + alias: "Fudd's PayPal", + }, + }, + }, +}; + +/** + * Token store: maps a serialized VC (the "token" string) to its metadata. + * The VC itself is the token — it cryptographically binds the payment method + * to the issuer. The store tracks the mandate association. + */ +const tokens: { + [token: string]: { + emailAddress: string; + paymentMethodAlias: string; + paymentMandateId: string | null; + }; +} = {}; + +/** + * Creates a token for an account by issuing a W3C Verifiable Credential. + * + * The VC embeds the payment method data as the credentialSubject and is + * signed with the credentials-provider's Ed25519 key. The serialized VC + * (JSON string) serves as the token. + * + * @param emailAddress - The email address of the account. + * @param paymentMethodAlias - The alias of the payment method. + * @returns The serialized VC token for the payment method. + */ +export const createToken = async ( + emailAddress: string, + paymentMethodAlias: string +): Promise => { + const paymentMethod = getPaymentMethodByAlias(emailAddress, paymentMethodAlias); + if (!paymentMethod) { + throw new Error( + `Payment method "${paymentMethodAlias}" not found for ${emailAddress}` + ); + } + + // Issue a W3C Verifiable Credential containing the payment method data. + const credential: VerifiableCredential = await issuePaymentCredential({ + subjectId: `mailto:${emailAddress}`, + paymentMethod: paymentMethod as unknown as Record, + }); + + // Serialize the VC to use as the token string + const token = JSON.stringify(credential); + + tokens[token] = { + emailAddress, + paymentMethodAlias, + paymentMandateId: null, + }; + + return token; +}; + +/** + * Updates the token with the payment mandate id. + * + * @param token - The token (serialized VC) to update. + * @param paymentMandateId - The payment mandate id to associate with the token. + */ +export const updateToken = (token: string, paymentMandateId: string): void => { + if (!(token in tokens)) { + throw new Error(`Token ${token} not found`); + } + if (tokens[token].paymentMandateId) { + // Do not overwrite the payment mandate id if it is already set. + return; + } + tokens[token].paymentMandateId = paymentMandateId; +}; + +/** + * Verify a token (serialized VC) and return the payment method. + * + * Performs cryptographic verification of the VC signature, checks the + * mandate binding, and extracts the payment method from the credential subject. + * + * @param token - The serialized VC token. + * @param paymentMandateId - The payment mandate id associated with the token. + * @returns The payment method extracted from the verified VC. + * @throws Error if the token/VC is invalid or mandate doesn't match. + */ +export const verifyToken = async ( + token: string, + paymentMandateId: string +): Promise => { + // Check the token store for mandate binding + const accountLookup = tokens[token]; + if (!accountLookup) { + throw new Error("Invalid token"); + } + if (accountLookup.paymentMandateId !== paymentMandateId) { + throw new Error("Invalid token"); + } + + // Cryptographically verify the VC + let credential: VerifiableCredential; + try { + credential = JSON.parse(token) as VerifiableCredential; + } catch { + throw new Error("Invalid token: not a valid VC"); + } + + const subject = await verifyAndExtractSubject(credential); + + // Reconstruct the PaymentMethod from the VC subject. + // Strip only the VC-specific `id` (subject DID/URI) and `paymentMandateId` + // metadata. The `type` field is kept because it holds the payment method + // type (e.g., "CARD"), not the JSON-LD type. + const { id: _id, paymentMandateId: _mandateId, ...paymentMethodData } = subject; + return paymentMethodData as unknown as PaymentMethod; +}; + +/** + * Returns a list of the payment methods for the given account email address. + * + * @param emailAddress - The account's email address. + * @returns A list of the user's payment_methods. + */ +export const getAccountPaymentMethods = ( + emailAddress: string +): PaymentMethod[] => { + const account = accountDb[emailAddress]; + if (!account || !account.payment_methods) { + return []; + } + return Object.values(account.payment_methods); +}; + +/** + * Gets the shipping address associated for the given account email address. + * + * @param emailAddress - The account's email address. + * @returns The account's shipping address. + */ +export const getAccountShippingAddress = ( + emailAddress: string +): Account["shipping_address"] | null => { + const account = accountDb[emailAddress]; + return account?.shipping_address || null; +}; + +/** + * Returns the payment method for a given account and alias. + * + * @param emailAddress - The account's email address. + * @param alias - The alias of the payment method to retrieve. + * @returns The payment method for the given account and alias, or null if not found. + */ +export const getPaymentMethodByAlias = ( + emailAddress: string, + alias: string +): PaymentMethod | null => { + const paymentMethods = getAccountPaymentMethods(emailAddress).filter( + (paymentMethod) => paymentMethod.alias.toLowerCase() === alias.toLowerCase() + ); + + if (paymentMethods.length === 0) { + return null; + } + + return paymentMethods[0]; +}; diff --git a/samples/typescript/src/roles/credentials-provider/agent.ts b/samples/typescript/src/roles/credentials-provider/agent.ts new file mode 100644 index 00000000..d28b4e31 --- /dev/null +++ b/samples/typescript/src/roles/credentials-provider/agent.ts @@ -0,0 +1,57 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LlmAgent } from '@google/adk'; +import { DEBUG_MODE_INSTRUCTIONS } from '../../common/constants/index.js'; +import { + handleCreatePaymentCredentialToken, + handleGetPaymentMethodRawCredentials, + handleGetShippingAddress, + handleSearchPaymentMethods, + handleSignedPaymentMandate, + handlePaymentReceipt, +} from './tools.js'; + +/** + * Credentials Provider Agent (ADK) + * + * This agent acts as a secure digital wallet managing user payment credentials. + */ +export const credentialsProviderAgent = new LlmAgent({ + name: 'credentials_provider_agent', + model: 'gemini-2.5-flash', + description: 'An agent that holds a user\'s payment credentials.', + instruction: `You are a credentials provider agent acting as a secure digital wallet. +Your job is to manage a user's payment methods and shipping addresses. + +IMPORTANT RULES: +1. Read the user request carefully and call EXACTLY ONE tool. +2. After the tool returns its result, respond with a single short + sentence confirming what was done. Example: "Shipping address retrieved." +3. NEVER call a tool more than once per request. +4. NEVER ask follow-up questions or continue the conversation. +5. Your entire response after the tool result must be one sentence. + +${DEBUG_MODE_INSTRUCTIONS}`, + tools: [ + handleCreatePaymentCredentialToken, + handleGetPaymentMethodRawCredentials, + handleGetShippingAddress, + handleSearchPaymentMethods, + handleSignedPaymentMandate, + handlePaymentReceipt, + ], +}); diff --git a/samples/typescript/src/roles/credentials-provider/server.ts b/samples/typescript/src/roles/credentials-provider/server.ts new file mode 100644 index 00000000..517e00fe --- /dev/null +++ b/samples/typescript/src/roles/credentials-provider/server.ts @@ -0,0 +1,144 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Runner } from '@google/adk'; +import type { AgentCard } from '@a2a-js/sdk'; + +import { credentialsProviderAgent } from './agent.js'; +import { sessionService } from '../../common/config/session.js'; +import { BaseAgentExecutor } from '../../common/server/base-executor.js'; +import { bootstrapServer } from '../../common/server/bootstrap.js'; +import { DATA_KEYS, A2A_DATA_KEYS } from '../../common/constants/index.js'; +import { initKeyManager } from '../../common/vc/index.js'; + +const runner = new Runner({ + appName: 'ap2-credentials-provider', + agent: credentialsProviderAgent, + sessionService, +}); + +const agentExecutor = new BaseAgentExecutor({ + agentName: 'credentials_provider_agent', + appName: 'ap2-credentials-provider', + runner, + maxLlmCalls: 3, + workingMessage: 'Processing request...', + preprocessMessage({ userText, dataParts }) { + // Build a hint about available data so the LLM knows which tool to call + const dataKeys = dataParts.flatMap((dp) => Object.keys(dp)); + const hasUserEmail = dataKeys.includes(A2A_DATA_KEYS.USER_EMAIL); + const hasPaymentMandate = dataKeys.includes(DATA_KEYS.PAYMENT_MANDATE); + const hasPaymentMethodData = dataKeys.includes(A2A_DATA_KEYS.PAYMENT_METHOD_DATA); + const hasPaymentMethodAlias = dataKeys.includes(A2A_DATA_KEYS.PAYMENT_METHOD_ALIAS); + + if (hasPaymentMandate && hasUserEmail) { + return `${userText}\n\nThe request data contains a PaymentMandate. Call the appropriate tool to handle it.`; + } + if (hasPaymentMethodData && hasUserEmail) { + return `${userText}\n\nThe request data contains user_email and payment method criteria. Call handleSearchPaymentMethods.`; + } + if (hasPaymentMethodAlias && hasUserEmail) { + return `${userText}\n\nThe request data contains user_email and payment_method_alias. Call handleCreatePaymentCredentialToken.`; + } + if (hasUserEmail) { + return `${userText}\n\nThe request data contains user_email. Call handleGetShippingAddress now.`; + } + return userText; + }, +}); + +const agentCard: AgentCard = { + name: 'CredentialsProvider', + description: "An agent that holds a user's payment credentials.", + url: 'http://localhost:8002', + provider: { organization: 'AP2 Demo', url: 'https://github.com/google-agentic-commerce/ap2' }, + skills: [ + { + id: 'initiate_payment', + name: 'Initiate Payment', + description: 'Initiates a payment with the correct payment processor.', + tags: ['payments'], + }, + { + id: 'get_eligible_payment_methods', + name: 'Get Eligible Payment Methods', + description: + 'Provides a list of eligible payment methods for a particular purchase.', + parameters: { + type: 'object', + properties: { + email_address: { + type: 'string', + description: + "The email address associated with the user's account.", + }, + }, + required: ['email_address'], + }, + tags: ['eligible', 'payment', 'methods'], + } as AgentCard['skills'][number], + { + id: 'get_account_shipping_address', + name: 'Get Shipping Address', + description: "Fetches the shipping address from a user's wallet.", + parameters: { + type: 'object', + properties: { + email_address: { + type: 'string', + description: + "The email address associated with the user's account.", + }, + }, + required: ['email_address'], + }, + tags: ['account', 'shipping'], + } as AgentCard['skills'][number], + ], + capabilities: { + streaming: true, + pushNotifications: false, + stateTransitionHistory: true, + extensions: [ + { + uri: 'https://github.com/google-agentic-commerce/ap2/v1', + description: 'Supports the Agent Payments Protocol.', + required: true, + }, + { + uri: 'https://sample-card-network.github.io/paymentmethod/common/types/v1', + description: + 'Supports the Sample Card Network payment method extension', + required: true, + }, + ], + }, + defaultInputModes: ['text/plain'], + defaultOutputModes: ['application/json'], + protocolVersion: '0.3.0', + version: '1.0.0', +}; + +// Initialize the VC key manager before starting the server so +// the credentials-provider can issue and verify Verifiable Credentials. +initKeyManager().then(() => { + bootstrapServer({ + agentCard, + agentExecutor, + port: 8002, + label: 'CredentialsProvider', + }); +}); diff --git a/samples/typescript/src/roles/credentials-provider/tools.ts b/samples/typescript/src/roles/credentials-provider/tools.ts new file mode 100644 index 00000000..1da48666 --- /dev/null +++ b/samples/typescript/src/roles/credentials-provider/tools.ts @@ -0,0 +1,360 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FunctionTool } from '@google/adk'; +import { z } from 'zod'; +import { v4 as uuidv4 } from 'uuid'; +import { + findDataPart, + findDataParts, + parseCanonicalObject, +} from '../../common/utils/message.js'; +import * as accountManager from './account-manager.js'; +import { paymentMandateSchema } from '../../common/schemas/payment-mandate.js'; +import { getA2AContextFromTool } from '../../common/server/a2a-context.js'; +import { DATA_KEYS, A2A_DATA_KEYS } from '../../common/constants/index.js'; + +const PAYMENT_MANDATE_DATA_KEY = DATA_KEYS.PAYMENT_MANDATE; +const PAYMENT_METHOD_DATA_DATA_KEY = A2A_DATA_KEYS.PAYMENT_METHOD_DATA; + +interface PaymentMethodData { + supportedMethods: string; + data: { + network?: string[]; + } & Record; +} + +// Helper functions +function getPaymentMethodAliases( + paymentMethods: accountManager.PaymentMethod[] +): (string | undefined)[] { + return paymentMethods.map((paymentMethod) => paymentMethod.alias); +} + +function paymentMethodIsEligible( + paymentMethod: accountManager.PaymentMethod, + merchantCriteria: PaymentMethodData +): boolean { + if (paymentMethod.type !== merchantCriteria.supportedMethods) { + return false; + } + + const merchantSupportedNetworks = (merchantCriteria.data?.network || []).map( + (network) => network.toLowerCase() + ); + + if (merchantSupportedNetworks.length === 0) { + return false; + } + + const paymentCardNetworks = paymentMethod.network || []; + for (const networkInfo of paymentCardNetworks) { + for (const supportedNetwork of merchantSupportedNetworks) { + if (networkInfo.name?.toLowerCase() === supportedNetwork) { + return true; + } + } + } + + return false; +} + +function getEligiblePaymentMethodAliases( + userEmail: string, + merchantAcceptedPaymentMethods: PaymentMethodData[] +): { payment_method_aliases: (string | undefined)[] } { + const paymentMethods = accountManager.getAccountPaymentMethods(userEmail); + const eligiblePaymentMethods: accountManager.PaymentMethod[] = []; + + for (const paymentMethod of paymentMethods) { + for (const criteria of merchantAcceptedPaymentMethods) { + if (paymentMethodIsEligible(paymentMethod, criteria)) { + eligiblePaymentMethods.push(paymentMethod); + break; + } + } + } + + return { + payment_method_aliases: getPaymentMethodAliases(eligiblePaymentMethods), + }; +} + +/** + * ADK Tools for Credentials Provider Agent + * + * Note: These tools are designed to work within the A2A protocol. + * The dataParts, eventBus, and currentTask are stored in session state + * by the AgentExecutor before calling the agent. + */ + +export const handleCreatePaymentCredentialToken = new FunctionTool({ + name: 'handleCreatePaymentCredentialToken', + description: 'Handles a request to get a payment credential token. Updates a task with the payment credential token.', + parameters: z.object({ + // Dummy parameter since ADK FunctionTool requires at least one parameter + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { dataParts, eventBus, currentTask } = getA2AContextFromTool(context); + + const userEmail = findDataPart("user_email", dataParts) as string | null; + const paymentMethodAlias = findDataPart( + "payment_method_alias", + dataParts + ) as string | null; + + if (!userEmail || !paymentMethodAlias) { + return { error: "user_email and payment_method_alias are required but were not found in the request data. Report this error to the caller." }; + } + + const tokenizedPaymentMethod = await accountManager.createToken( + userEmail, + paymentMethodAlias + ); + + // Publish artifact via A2A eventBus + if (eventBus) { + eventBus.publish({ + kind: "artifact-update", + taskId: currentTask?.id, + contextId: currentTask?.contextId, + artifact: { + artifactId: uuidv4(), + parts: [{ kind: "data", data: { token: tokenizedPaymentMethod } }], + }, + }); + } + + return { tokenizedPaymentMethod }; + }, +}); + +export const handleGetPaymentMethodRawCredentials = new FunctionTool({ + name: 'handleGetPaymentMethodRawCredentials', + description: 'Handles a request to get the raw credentials for a payment method. Updates a task with the payment method\'s raw credentials.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { dataParts, eventBus, currentTask } = getA2AContextFromTool(context); + + let paymentMandate; + try { + paymentMandate = parseCanonicalObject( + PAYMENT_MANDATE_DATA_KEY, + dataParts, + paymentMandateSchema + ); + } catch { + return { error: "PaymentMandate not found in request data. Report this error to the caller." }; + } + const paymentMandateContents = paymentMandate.paymentMandateContents; + + // Extract token value from nested {value, url} structure + const tokenObj = paymentMandateContents.paymentResponse.details?.token as + | { value?: string; url?: string } + | string + | undefined; + const token = typeof tokenObj === 'object' ? (tokenObj?.value ?? '') : (tokenObj ?? ''); + const paymentMandateId = paymentMandateContents.paymentMandateId; + + const paymentMethod = await accountManager.verifyToken(token, paymentMandateId); + + if (!paymentMethod) { + return { error: "Payment method not found for the given token. Report this error to the caller." }; + } + + if (eventBus) { + eventBus.publish({ + kind: "artifact-update", + taskId: currentTask?.id, + contextId: currentTask?.contextId, + artifact: { + artifactId: uuidv4(), + parts: [{ kind: "data", data: paymentMethod }], + }, + }); + } + + return { paymentMethod }; + }, +}); + +export const handleGetShippingAddress = new FunctionTool({ + name: 'handleGetShippingAddress', + description: 'Handles a request to get the user\'s shipping address. Updates a task with the user\'s shipping address.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { dataParts, eventBus, currentTask } = getA2AContextFromTool(context); + + const userEmail = findDataPart("user_email", dataParts) as string | null; + if (!userEmail) { + return { error: "user_email is required but was not found in the request data. Report this error to the caller." }; + } + + const shippingAddress = accountManager.getAccountShippingAddress(userEmail); + if (!shippingAddress) { + return { error: `Shipping address not found for user ${userEmail}. Report this error to the caller.` }; + } + + if (eventBus) { + eventBus.publish({ + kind: "artifact-update", + taskId: currentTask?.id, + contextId: currentTask?.contextId, + artifact: { + artifactId: uuidv4(), + parts: [{ kind: "data", data: { [DATA_KEYS.CONTACT_ADDRESS]: shippingAddress } }], + }, + }); + } + + return { shippingAddress }; + }, +}); + +export const handleSearchPaymentMethods = new FunctionTool({ + name: 'handleSearchPaymentMethods', + description: `Returns the user's payment methods that match what the merchant accepts. + +The merchant's accepted payment methods are provided in the data_parts as a +list of PaymentMethodData objects. The user's account is identified by the +user_email provided in the data_parts. + +This tool finds and returns all the payment methods associated with the user's +account that match the merchant's accepted payment methods.`, + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { dataParts, eventBus, currentTask } = getA2AContextFromTool(context); + + const userEmail = findDataPart("user_email", dataParts) as string | null; + const methodData = findDataParts(PAYMENT_METHOD_DATA_DATA_KEY, dataParts); + + if (!userEmail) { + return { error: "user_email is required for search_payment_methods but was not found in the request data. Report this error to the caller." }; + } + if (!methodData || methodData.length === 0) { + return { error: "method_data is required for search_payment_methods but was not found in the request data. Report this error to the caller." }; + } + + const merchantMethodDataList = methodData.map( + (data) => data as PaymentMethodData + ); + + const eligibleAliases = getEligiblePaymentMethodAliases( + userEmail, + merchantMethodDataList + ); + + if (eventBus) { + eventBus.publish({ + kind: "artifact-update", + taskId: currentTask?.id, + contextId: currentTask?.contextId, + artifact: { + artifactId: uuidv4(), + parts: [{ kind: "data", data: eligibleAliases }], + }, + }); + } + + return { eligibleAliases }; + }, +}); + +export const handleSignedPaymentMandate = new FunctionTool({ + name: 'handleSignedPaymentMandate', + description: 'Handles a signed payment mandate. Adds the payment mandate id to the token in storage and then completes the task.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { dataParts, eventBus, currentTask } = getA2AContextFromTool(context); + + let paymentMandate; + try { + paymentMandate = parseCanonicalObject( + PAYMENT_MANDATE_DATA_KEY, + dataParts, + paymentMandateSchema + ); + } catch { + return { error: "PaymentMandate not found in request data. Report this error to the caller." }; + } + + // x402 short-circuit: no token processing needed + const methodName = paymentMandate.paymentMandateContents.paymentResponse.methodName; + if (methodName === 'https://www.x402.org/') { + return { success: true, message: 'x402 payment mandate received, no token update needed.' }; + } + + // Extract token value from nested {value, url} structure + const tokenObj = paymentMandate.paymentMandateContents.paymentResponse.details?.token as + | { value?: string; url?: string } + | string + | undefined; + const token = typeof tokenObj === 'object' ? (tokenObj?.value ?? '') : (tokenObj ?? ''); + const paymentMandateId = paymentMandate.paymentMandateContents.paymentMandateId; + + accountManager.updateToken(token, paymentMandateId); + + if (eventBus) { + eventBus.publish({ + kind: "artifact-update", + taskId: currentTask?.id, + contextId: currentTask?.contextId, + artifact: { + artifactId: uuidv4(), + parts: [ + { + kind: "data", + data: { + status: "signed_payment_mandate_received", + paymentMandateId: paymentMandateId, + }, + }, + ], + }, + }); + } + + return { + success: true, + message: "Signed payment mandate validated and stored successfully.", + }; + }, +}); + +export const handlePaymentReceipt = new FunctionTool({ + name: 'handlePaymentReceipt', + description: 'Handles a payment receipt. This is a placeholder that completes the task without any action.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async () => { + return { success: true, message: 'Payment receipt acknowledged.' }; + }, +}); diff --git a/samples/typescript/src/roles/index.ts b/samples/typescript/src/roles/index.ts new file mode 100644 index 00000000..c7222e46 --- /dev/null +++ b/samples/typescript/src/roles/index.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** Agent card URLs for server agents in the AP2 system. */ +export const AGENT_URLS = { + CREDENTIALS_PROVIDER: 'http://localhost:8002/.well-known/agent-card.json', + PAYMENT_PROCESSOR: 'http://localhost:8003/.well-known/agent-card.json', + MERCHANT: 'http://localhost:8004/.well-known/agent-card.json', +} as const; diff --git a/samples/typescript/src/roles/merchant/agent.ts b/samples/typescript/src/roles/merchant/agent.ts new file mode 100644 index 00000000..9671f5c9 --- /dev/null +++ b/samples/typescript/src/roles/merchant/agent.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LlmAgent } from '@google/adk'; +import { DEBUG_MODE_INSTRUCTIONS } from '../../common/constants/index.js'; +import { findItemsWorkflow, updateCart, initiatePayment, dpcFinish } from './tools.js'; + +/** + * Merchant Agent (ADK) + * + * A sales assistant agent for merchants. Handles catalog search, cart updates, + * and payment processing. + */ +export const merchantAgent = new LlmAgent({ + name: 'merchant_agent', + model: 'gemini-2.5-flash', + description: 'A sales assistant agent for a merchant.', + instruction: `You are a merchant sales assistant agent. Your role is to help customers find products, manage their shopping cart, and complete purchases. + +Based on the customer's request, select the appropriate tool to call: + +1. **findItemsWorkflow**: Use when the customer wants to search for products or when you receive an IntentMandate. This generates product recommendations. + +2. **updateCart**: Use when the customer provides a shipping address and you need to update the cart with shipping costs and taxes. + +3. **initiatePayment**: Use when the customer wants to complete their purchase and you have a PaymentMandate. + +4. **dpcFinish**: Use when you receive a DPC response to finalize the payment. + +Important: +- If you detect a PaymentMandate in the request, immediately call initiatePayment +- Only call one tool per request based on the current context +- After the tool returns a result, respond with a brief summary of the outcome. Do not call the same tool again after it has already returned. + +${DEBUG_MODE_INSTRUCTIONS}`, + tools: [findItemsWorkflow, updateCart, initiatePayment, dpcFinish], +}); diff --git a/samples/typescript/src/roles/merchant/server.ts b/samples/typescript/src/roles/merchant/server.ts new file mode 100644 index 00000000..edb02708 --- /dev/null +++ b/samples/typescript/src/roles/merchant/server.ts @@ -0,0 +1,190 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { v4 as uuidv4 } from 'uuid'; +import { Runner } from '@google/adk'; +import type { + AgentCard, + Message, + TaskStatusUpdateEvent, +} from '@a2a-js/sdk'; + +import { merchantAgent } from './agent.js'; +import { sessionService } from '../../common/config/session.js'; +import { BaseAgentExecutor } from '../../common/server/base-executor.js'; +import { bootstrapServer } from '../../common/server/bootstrap.js'; +import { DATA_KEYS } from '../../common/constants/index.js'; + +const runner = new Runner({ + appName: 'ap2-merchant', + agent: merchantAgent, + sessionService, +}); + +const agentExecutor = new BaseAgentExecutor({ + agentName: 'merchant_agent', + appName: 'ap2-merchant', + runner, + maxLlmCalls: 5, + workingMessage: 'The merchant is processing your request...', + preprocessMessage({ userText, dataParts }) { + // If PaymentMandate is present, instruct agent to call initiatePayment immediately + const hasPaymentMandate = dataParts.some( + (dp) => DATA_KEYS.PAYMENT_MANDATE in dp + ); + if (hasPaymentMandate) { + return 'A PaymentMandate is present in the data. Call the initiatePayment tool immediately.'; + } + // If IntentMandate is present, instruct agent to call findItemsWorkflow immediately + const hasIntentMandate = dataParts.some( + (dp) => DATA_KEYS.INTENT_MANDATE in dp + ); + if (hasIntentMandate) { + return 'An IntentMandate is present in the data. Call the findItemsWorkflow tool immediately to find matching products.'; + } + return userText; + }, + postprocessResult({ + responseText, + toolWasCalled, + lastToolResult, + eventBus, + taskId, + contextId, + }) { + // Handle payment input-required status (OTP challenge) + if (lastToolResult?.status === 'input-required') { + const inputRequiredMessage: Message = { + kind: 'message', + role: 'agent', + messageId: uuidv4(), + parts: [ + { + kind: 'text', + text: 'A payment challenge has been raised. Please provide the OTP to complete the transaction. (Hint: the code is 123)', + }, + ], + taskId, + contextId, + }; + + eventBus.publish({ + kind: 'status-update', + taskId, + contextId, + status: { + state: 'input-required', + message: inputRequiredMessage, + timestamp: new Date().toISOString(), + }, + final: true, + } satisfies TaskStatusUpdateEvent); + + return null; // Signal: already handled + } + + // If the initiatePayment tool already published a terminal status-update + // (completed/failed) directly via eventBus, suppress the default to avoid + // double-publishing. + const terminalToolStates = ['completed', 'failed', 'canceled', 'rejected']; + if (lastToolResult?.status && terminalToolStates.includes(lastToolResult.status as string)) { + return null; // Signal: already handled by the tool + } + + // Determine final response text + let finalText = responseText || 'Completed.'; + if (toolWasCalled && !responseText) { + finalText = 'Request processed successfully.'; + } + + const agentMessage: Message = { + kind: 'message', + role: 'agent', + messageId: uuidv4(), + parts: [{ kind: 'text', text: finalText }], + taskId, + contextId, + }; + + return { + kind: 'status-update', + taskId, + contextId, + status: { + state: 'completed', + message: agentMessage, + timestamp: new Date().toISOString(), + }, + final: true, + } satisfies TaskStatusUpdateEvent; + }, +}); + +const agentCard: AgentCard = { + name: 'MerchantAgent', + description: 'A sales assistant agent for a merchant.', + url: 'http://localhost:8004', + provider: { organization: 'AP2 Demo', url: 'https://github.com/google-agentic-commerce/ap2' }, + skills: [ + { + id: 'search_catalog', + name: 'Search Catalog', + description: + "Searches the merchant's catalog based on a shopping intent & returns a cart containing the top results.", + parameters: { + type: 'object', + properties: { + shopping_intent: { + type: 'string', + description: + "A JSON string representing the user's shopping intent.", + }, + }, + required: ['shopping_intent'], + }, + tags: ['merchant', 'search', 'catalog'], + } as AgentCard['skills'][number], + ], + capabilities: { + streaming: true, + pushNotifications: false, + stateTransitionHistory: true, + extensions: [ + { + uri: 'https://github.com/google-agentic-commerce/ap2/v1', + description: 'Supports the Agent Payments Protocol.', + required: true, + }, + { + uri: 'https://sample-card-network.github.io/paymentmethod/common/types/v1', + description: + 'Supports the Sample Card Network payment method extension', + required: true, + }, + ], + }, + defaultInputModes: ['application/json'], + defaultOutputModes: ['application/json'], + protocolVersion: '0.3.0', + version: '1.0.0', +}; + +bootstrapServer({ + agentCard, + agentExecutor, + port: 8004, + label: 'Merchant', +}); diff --git a/samples/typescript/src/roles/merchant/storage.ts b/samples/typescript/src/roles/merchant/storage.ts new file mode 100644 index 00000000..714d7db0 --- /dev/null +++ b/samples/typescript/src/roles/merchant/storage.ts @@ -0,0 +1,57 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * In-memory storage for CartMandates and risk data. + * + * A CartMandate may be updated multiple times during the course of a shopping + * journey. This storage system is used to persist CartMandates between + * interactions between the shopper and merchant agents. + */ + +import type { CartMandate } from "../../common/types/cart-mandate.js"; + +// Separate stores for type safety — avoids union-type ambiguity +const cartMandateStore = new Map(); +const riskDataStore = new Map(); + +/** + * Get a cart mandate by cart ID. + */ +export function getCartMandate(cartId: string): CartMandate | undefined { + return cartMandateStore.get(cartId); +} + +/** + * Set a cart mandate by cart ID. + */ +export function setCartMandate(cartId: string, cartMandate: CartMandate): void { + cartMandateStore.set(cartId, cartMandate); +} + +/** + * Set risk data by context ID. + */ +export function setRiskData(contextId: string, riskData: string): void { + riskDataStore.set(contextId, riskData); +} + +/** + * Get risk data by context ID. + */ +export function getRiskData(contextId: string): string | undefined { + return riskDataStore.get(contextId); +} diff --git a/samples/typescript/src/roles/merchant/tools.ts b/samples/typescript/src/roles/merchant/tools.ts new file mode 100644 index 00000000..fd1b6e66 --- /dev/null +++ b/samples/typescript/src/roles/merchant/tools.ts @@ -0,0 +1,542 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FunctionTool } from '@google/adk'; +import { z } from 'zod'; +import { v4 as uuidv4 } from 'uuid'; +import { A2AClient } from '@a2a-js/sdk/client'; +import type { + MessageSendParams, + SendMessageSuccessResponse, + Task, + TaskArtifactUpdateEvent, + TaskStatusUpdateEvent, +} from '@a2a-js/sdk'; +import { findDataPart, parseCanonicalObject } from '../../common/utils/message.js'; +import { getA2AContextFromTool } from '../../common/server/a2a-context.js'; +import type { PaymentItem } from '../../common/types/payment-item.js'; +import type { CartMandate } from '../../common/types/cart-mandate.js'; +import type { IntentMandate } from '../../common/types/intent-mandate.js'; +import type { PaymentMandate } from '../../common/types/payment-mandate.js'; +import { intentMandateSchema } from '../../common/schemas/intent-mandate.js'; +import { paymentMandateSchema } from '../../common/schemas/payment-mandate.js'; +import { getCartMandate, getRiskData, setCartMandate, setRiskData } from './storage.js'; +import { GoogleGenAI } from '@google/genai'; +import { AGENT_URLS } from '../index.js'; +import { DATA_KEYS } from '../../common/constants/index.js'; + +/** Known shopping agent IDs allowed to interact with this merchant. */ +const KNOWN_SHOPPING_AGENTS = new Set(['trusted_shopping_agent']); + +const FAKE_JWT = 'eyJhbGciOiJSUzI1NiIsImtpZIwMjQwOTA...'; +const INTENT_MANDATE_DATA_KEY = DATA_KEYS.INTENT_MANDATE; +const CART_MANDATE_DATA_KEY = DATA_KEYS.CART_MANDATE; +const PAYMENT_MANDATE_DATA_KEY = DATA_KEYS.PAYMENT_MANDATE; + +const PAYMENT_PROCESSORS_BY_PAYMENT_METHOD_TYPE: Record = { + CARD: AGENT_URLS.PAYMENT_PROCESSOR, + 'https://www.x402.org/': AGENT_URLS.PAYMENT_PROCESSOR, +}; + +// Lazy initialization of Gemini for product generation +function getGenAI() { + const apiKey = process.env.GOOGLE_GENAI_API_KEY || process.env.GEMINI_API_KEY; + if (!apiKey) { + throw new Error('GOOGLE_GENAI_API_KEY or GEMINI_API_KEY environment variable is required for product generation'); + } + return new GoogleGenAI({ apiKey }); +} + +// Helper functions +function getPaymentProcessorTaskId(task: Task | undefined): string | null { + if (!task || !task.history) { + return null; + } + + for (const message of task.history) { + if (message.taskId && message.taskId !== task.id) { + return message.taskId; + } + } + + return null; +} + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +/** + * Tool 1: Find Items Workflow + * + * Generates products matching the user's IntentMandate using Gemini. + */ +export const findItemsWorkflow = new FunctionTool({ + name: 'findItemsWorkflow', + description: "Finds products that match the user's IntentMandate by generating realistic product recommendations. Pass the user's shopping intent description.", + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + intentDescription: z.string().optional().describe("The user's shopping intent description (used in ADK dev mode when no A2A data parts are available)."), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { dataParts, eventBus, currentTask } = getA2AContextFromTool(context); + + const taskId = currentTask?.id || uuidv4(); + const contextId = currentTask?.contextId || uuidv4(); + + // Validate shopping agent ID + const shoppingAgentId = findDataPart('shopping_agent_id', dataParts) as string | null; + if (shoppingAgentId && !KNOWN_SHOPPING_AGENTS.has(shoppingAgentId)) { + return { error: `Unknown shopping agent: ${shoppingAgentId}. Report this error to the caller.` }; + } + + // Parse IntentMandate from A2A data parts, or fall back to direct parameter + let intent: string; + try { + const intentMandate = parseCanonicalObject( + INTENT_MANDATE_DATA_KEY, + dataParts, + intentMandateSchema + ); + intent = intentMandate.naturalLanguageDescription; + } catch { + // ADK dev mode fallback: use the intent description parameter directly + if (input.intentDescription) { + intent = input.intentDescription; + } else { + return { error: 'IntentMandate not found in request data and no intentDescription provided. Report this error to the caller.' }; + } + } + + // Generate products using Gemini + const genAI = getGenAI(); + + const prompt = `Based on the user's request for '${intent}', generate 3 complete, unique and realistic PaymentItem JSON objects. + +You MUST exclude all branding from the PaymentItem label field. + +Generate realistic, diverse product data with reasonable prices and details. + +Return a JSON array where each object has: label (string), amount (object with currency string and value number), and refundPeriod (number in days).`; + + // Retry mechanism + const maxRetries = 3; + const retryDelay = 1000; + let items: PaymentItem[] = []; + + for (let attempt = 0; attempt < maxRetries; attempt++) { + try { + const result = await genAI.models.generateContent({ + model: 'gemini-2.5-flash', + contents: prompt, + config: { + responseMimeType: 'application/json', + }, + }); + const text = result.text ?? ''; + items = JSON.parse(text) as PaymentItem[]; + break; + } catch (error) { + if (attempt === maxRetries - 1) { + return { error: `Unable to generate products after ${maxRetries} attempts: ${error instanceof Error ? error.message : String(error)}. Report this error to the caller.` }; + } + await sleep(retryDelay * (attempt + 1)); + } + } + + if (!items || items.length === 0) { + return { error: 'No products were generated. Report this error to the caller.' }; + } + + // Create cart mandates for each item + const currentTime = new Date(); + let itemCount = 0; + + for (const item of items) { + itemCount++; + const cartExpiryTime = new Date(currentTime.getTime() + 30 * 60 * 1000); + + const cartMandate: CartMandate = { + contents: { + id: `cart_${itemCount}`, + userCartConfirmationRequired: true, + paymentRequest: { + methodData: [ + { + supportedMethods: 'CARD', + data: { + network: ['mastercard', 'paypal', 'amex'], + }, + }, + ], + details: { + id: `order_${itemCount}`, + displayItems: [item], + shippingOptions: [], + modifiers: [], + total: { + label: 'Total', + amount: item.amount, + pending: false, + refundPeriod: item.refundPeriod, + }, + }, + options: { + requestShipping: true, + }, + }, + cartExpiry: cartExpiryTime.toISOString(), + merchantName: 'Generic Merchant', + }, + }; + + setCartMandate(cartMandate.contents.id, cartMandate); + + const artifactUpdate: TaskArtifactUpdateEvent = { + kind: 'artifact-update', + taskId, + contextId, + artifact: { + artifactId: uuidv4(), + parts: [ + { + kind: 'data', + data: { + [CART_MANDATE_DATA_KEY]: cartMandate, + }, + }, + ], + }, + }; + + eventBus.publish(artifactUpdate); + } + + // Add risk data + const riskData = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...fake_risk_data'; + setRiskData(contextId, riskData); + + const riskDataArtifact: TaskArtifactUpdateEvent = { + kind: 'artifact-update', + taskId, + contextId, + artifact: { + artifactId: uuidv4(), + parts: [ + { + kind: 'data', + data: { risk_data: riskData }, + }, + ], + }, + }; + eventBus.publish(riskDataArtifact); + + return { status: 'success', itemCount }; + }, +}); + +/** + * Tool 2: Update Cart + * + * Updates cart with shipping address and adds tax/shipping costs. + */ +export const updateCart = new FunctionTool({ + name: 'updateCart', + description: 'Updates an existing cart after a shipping address is provided.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { dataParts, eventBus, currentTask } = getA2AContextFromTool(context); + + const cartId = findDataPart('cart_id', dataParts) as string | null; + if (!cartId) { + return { error: 'Missing cart_id in request data. Report this error to the caller.' }; + } + + const shippingAddress = findDataPart( + 'shipping_address', + dataParts + ) as Record | null; + if (!shippingAddress) { + return { error: 'Missing shipping address in request data. Report this error to the caller.' }; + } + + const cartMandate = getCartMandate(cartId); + if (!cartMandate) { + return { error: `CartMandate not found for cart_id: ${cartId}. Report this error to the caller.` }; + } + + const riskData = getRiskData(currentTask.contextId); + if (!riskData) { + return { error: `Missing risk_data for context_id: ${currentTask.contextId}. Report this error to the caller.` }; + } + + // Cast shipping address from A2A data parts to the expected schema type + cartMandate.contents.paymentRequest.shippingAddress = shippingAddress as typeof cartMandate.contents.paymentRequest.shippingAddress; + + const taxAndShippingCosts: PaymentItem[] = [ + { + label: 'Shipping', + amount: { + currency: 'USD', + value: 2.0, + }, + refundPeriod: 30, + }, + { + label: 'Tax', + amount: { + currency: 'USD', + value: 1.5, + }, + refundPeriod: 30, + }, + ]; + + const paymentRequest = cartMandate.contents.paymentRequest; + + if (!paymentRequest.details.displayItems) { + paymentRequest.details.displayItems = taxAndShippingCosts; + } else { + paymentRequest.details.displayItems.push(...taxAndShippingCosts); + } + + paymentRequest.details.total.amount.value = + paymentRequest.details.displayItems.reduce( + (acc, curr) => acc + curr.amount.value, + 0 + ); + + cartMandate.merchantAuthorization = FAKE_JWT; + + const artifactUpdate: TaskArtifactUpdateEvent = { + kind: 'artifact-update', + taskId: currentTask.id, + contextId: currentTask.contextId, + artifact: { + artifactId: uuidv4(), + parts: [ + { + kind: 'data', + data: { + [CART_MANDATE_DATA_KEY]: cartMandate, + }, + }, + { + kind: 'data', + data: { + risk_data: riskData, + }, + }, + ], + }, + }; + + eventBus.publish(artifactUpdate); + + return { status: 'success', cartMandate }; + }, +}); + +/** + * Tool 3: Initiate Payment + * + * Delegates payment processing to the payment processor agent. + */ +export const initiatePayment = new FunctionTool({ + name: 'initiatePayment', + description: 'Initiates a payment for a given payment mandate by delegating to the payment processor.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { dataParts, eventBus, currentTask } = getA2AContextFromTool(context); + + let paymentMandate: PaymentMandate; + try { + paymentMandate = parseCanonicalObject( + PAYMENT_MANDATE_DATA_KEY, + dataParts, + paymentMandateSchema + ); + } catch { + return { error: 'PaymentMandate not found in request data. Report this error to the caller.' }; + } + + const riskData = findDataPart('risk_data', dataParts) as string | null; + if (!riskData) { + return { error: 'Missing risk_data in request data. Report this error to the caller.' }; + } + + const paymentMethodType = + paymentMandate.paymentMandateContents.paymentResponse.methodName; + + const processorUrl = + PAYMENT_PROCESSORS_BY_PAYMENT_METHOD_TYPE[paymentMethodType]; + + if (!processorUrl) { + return { error: `No payment processor found for method: ${paymentMethodType}. Report this error to the caller.` }; + } + + const client = await A2AClient.fromCardUrl(processorUrl); + + const message: MessageSendParams = { + message: { + messageId: uuidv4(), + role: 'user', + contextId: currentTask.contextId, + parts: [ + { + kind: 'text', + text: 'Call the initiatePayment tool to process this payment. The payment mandate and risk data are provided in the data parts of this message.', + }, + { + kind: 'data', + data: { + [PAYMENT_MANDATE_DATA_KEY]: paymentMandate, + }, + }, + { + kind: 'data', + data: { + risk_data: riskData, + }, + }, + ], + kind: 'message', + }, + }; + + const challengeResponse = findDataPart('challenge_response', dataParts) as + | string + | null; + if (challengeResponse) { + message.message.parts.push({ + kind: 'data', + data: { challenge_response: challengeResponse }, + }); + } + + const paymentProcessorTaskId = getPaymentProcessorTaskId(currentTask); + if (paymentProcessorTaskId) { + message.message.taskId = paymentProcessorTaskId; + } + + const response = await client.sendMessage(message); + + if ('error' in response) { + return { error: `Payment processor error: ${response.error.message}. Report this error to the caller.` }; + } + + const result = (response as SendMessageSuccessResponse).result; + if (result.kind === 'task') { + const task = result as Task; + + // Forward PaymentReceipt artifacts from payment processor to shopping agent + for (const artifact of task.artifacts ?? []) { + for (const part of artifact.parts) { + if (part.kind === 'data') { + const data = (part as { kind: 'data'; data: Record }).data; + if (data[DATA_KEYS.PAYMENT_RECEIPT]) { + const receiptArtifact: TaskArtifactUpdateEvent = { + kind: 'artifact-update', + taskId: currentTask.id, + contextId: currentTask.contextId, + artifact: { + artifactId: uuidv4(), + parts: [{ kind: 'data', data: { [DATA_KEYS.PAYMENT_RECEIPT]: data[DATA_KEYS.PAYMENT_RECEIPT] } }], + }, + }; + eventBus.publish(receiptArtifact); + } + } + } + } + + const statusUpdate: TaskStatusUpdateEvent = { + kind: 'status-update', + taskId: currentTask.id, + contextId: currentTask.contextId, + status: { + state: task.status.state, + message: task.status.message, + timestamp: new Date().toISOString(), + }, + final: false, + }; + + const terminalStates = ['completed', 'failed', 'canceled', 'rejected']; + if (terminalStates.includes(task.status.state)) { + statusUpdate.final = true; + } + + eventBus.publish(statusUpdate); + + return { + status: task.status.state, + taskId: task.id, + }; + } + + return { error: 'Unexpected response type from payment processor. Report this error to the caller.' }; + }, +}); + +/** + * Tool 4: DPC Finish + * + * Receives and validates DPC response to finalize payment. + */ +export const dpcFinish = new FunctionTool({ + name: 'dpcFinish', + description: 'Receives and validates a DPC response to finalize payment. This receives the Digital Payment Credential (DPC) response and simulates payment finalization.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { dataParts, eventBus, currentTask } = getA2AContextFromTool(context); + + const dpcResponse = findDataPart('dpc_response', dataParts) as Record< + string, + unknown + > | null; + if (!dpcResponse) { + return { error: 'Missing dpc_response in request data. Report this error to the caller.' }; + } + + const artifactUpdate: TaskArtifactUpdateEvent = { + kind: 'artifact-update', + taskId: currentTask.id, + contextId: currentTask.contextId, + artifact: { + artifactId: uuidv4(), + parts: [ + { + kind: 'data', + data: { paymentStatus: 'SUCCESS', transactionId: 'txn_1234567890' }, + }, + ], + }, + }; + eventBus.publish(artifactUpdate); + + return { status: 'success' }; + }, +}); diff --git a/samples/typescript/src/roles/payment-processor/agent.ts b/samples/typescript/src/roles/payment-processor/agent.ts new file mode 100644 index 00000000..7f75377f --- /dev/null +++ b/samples/typescript/src/roles/payment-processor/agent.ts @@ -0,0 +1,41 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LlmAgent } from "@google/adk"; +import { DEBUG_MODE_INSTRUCTIONS } from "../../common/constants/index.js"; +import { initiatePayment } from "./tools.js"; + +/** + * Payment Processor Agent (ADK) + * + * Processes card payments on behalf of merchants with OTP challenge support. + */ +export const paymentProcessorAgent = new LlmAgent({ + name: "payment_processor_agent", + model: "gemini-2.5-flash", + description: "An agent that processes card payments on behalf of a merchant.", + instruction: `You are a payment processor agent that handles card payments. + +When you receive a payment mandate, call the initiatePayment tool to process it. +The tool will handle the OTP challenge flow automatically. + +Call the initiatePayment tool exactly once per request. After the tool returns +a result, respond with a brief summary of the outcome. Do not call the tool +again after it has already returned. + +${DEBUG_MODE_INSTRUCTIONS}`, + tools: [initiatePayment], +}); diff --git a/samples/typescript/src/roles/payment-processor/server.ts b/samples/typescript/src/roles/payment-processor/server.ts new file mode 100644 index 00000000..653d61ed --- /dev/null +++ b/samples/typescript/src/roles/payment-processor/server.ts @@ -0,0 +1,91 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Runner } from '@google/adk'; +import type { AgentCard } from '@a2a-js/sdk'; + +import { paymentProcessorAgent } from './agent.js'; +import { sessionService } from '../../common/config/session.js'; +import { BaseAgentExecutor } from '../../common/server/base-executor.js'; +import { bootstrapServer } from '../../common/server/bootstrap.js'; + +const runner = new Runner({ + appName: 'ap2-payment-processor', + agent: paymentProcessorAgent, + sessionService, +}); + +const agentExecutor = new BaseAgentExecutor({ + agentName: 'payment_processor_agent', + appName: 'ap2-payment-processor', + runner, + maxLlmCalls: 3, + workingMessage: 'Processing payment...', + postprocessResult({ lastToolResult, responseText: _responseText, toolWasCalled: _toolWasCalled }) { + // If tool returned input-required or completed, it already published + // the status update directly via eventBus -- suppress default handling + if (lastToolResult?.status === 'input-required' || lastToolResult?.status === 'completed') { + return null; // Signal: already handled + } + + // Otherwise, let the base class handle default completion + return undefined; + }, +}); + +const agentCard: AgentCard = { + name: 'MerchantPaymentProcessorAgent', + description: 'An agent that processes card payments on behalf of a merchant.', + url: 'http://localhost:8003', + provider: { organization: 'AP2 Demo', url: 'https://github.com/google-agentic-commerce/ap2' }, + skills: [ + { + id: 'card-processor', + name: 'Card Processor', + description: 'Processes card payments.', + tags: ['payment', 'card'], + }, + ], + capabilities: { + streaming: true, + pushNotifications: false, + stateTransitionHistory: true, + extensions: [ + { + uri: 'https://github.com/google-agentic-commerce/ap2/v1', + description: 'Supports the Agent Payments Protocol.', + required: true, + }, + { + uri: 'https://sample-card-network.github.io/paymentmethod/common/types/v1', + description: + 'Supports the Sample Card Network payment method extension', + required: true, + }, + ], + }, + defaultInputModes: ['text/plain'], + defaultOutputModes: ['application/json'], + protocolVersion: '0.3.0', + version: '1.0.0', +}; + +bootstrapServer({ + agentCard, + agentExecutor, + port: 8003, + label: 'PaymentProcessor', +}); diff --git a/samples/typescript/src/roles/payment-processor/tools.ts b/samples/typescript/src/roles/payment-processor/tools.ts new file mode 100644 index 00000000..5ec07750 --- /dev/null +++ b/samples/typescript/src/roles/payment-processor/tools.ts @@ -0,0 +1,396 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FunctionTool } from "@google/adk"; +import { z } from "zod"; +import { v4 as uuidv4 } from "uuid"; +import { A2AClient } from "@a2a-js/sdk/client"; +import type { + MessageSendParams, + SendMessageSuccessResponse, + Task, + TaskArtifactUpdateEvent, + TaskStatusUpdateEvent, +} from "@a2a-js/sdk"; +import type { ExecutionEventBus } from "@a2a-js/sdk/server"; +import { findDataPart, parseCanonicalObject } from "../../common/utils/message.js"; +import { getA2AContextFromTool } from "../../common/server/a2a-context.js"; +import type { PaymentMandate } from "../../common/types/payment-mandate.js"; +import type { PaymentReceipt } from "../../common/types/payment-receipt.js"; +import { paymentMandateSchema } from "../../common/schemas/payment-mandate.js"; +import { DATA_KEYS } from "../../common/constants/index.js"; + +const PAYMENT_MANDATE_DATA_KEY = DATA_KEYS.PAYMENT_MANDATE; +const PAYMENT_RECEIPT_DATA_KEY = DATA_KEYS.PAYMENT_RECEIPT; + +// TODO(production): Replace with real OTP verification against issuer/network API. +function challengeResponseIsValid(challengeResponse: string): boolean { + return challengeResponse === "123"; // Demo-only hardcoded OTP +} + +function getCredentialsProviderUrl( + paymentMandate: PaymentMandate +): string | null { + const details = paymentMandate.paymentMandateContents.paymentResponse.details; + if (!details) return null; + const tokenObj = details.token as + | { value?: string; url?: string } + | string + | undefined; + if (typeof tokenObj === "object" && tokenObj?.url) { + return tokenObj.url; + } + return null; +} + +/** + * Request payment credentials from the credentials provider. + * For x402, the signed payload is already in payment_response.details. + */ +async function requestPaymentCredential( + paymentMandate: PaymentMandate, + contextId: string, + debugMode: boolean = false, + paymentMethod: string = "CARD" +): Promise { + if (paymentMethod === "x402") { + // For x402, the signed payload is already in the payment_response.details + return paymentMandate.paymentMandateContents.paymentResponse.details?.value; + } + + const credentialsProviderUrl = getCredentialsProviderUrl(paymentMandate); + if (!credentialsProviderUrl) { + throw new Error( + "Could not resolve credentials provider URL from payment mandate token." + ); + } + + const client = await A2AClient.fromCardUrl(credentialsProviderUrl); + + const message: MessageSendParams = { + message: { + messageId: uuidv4(), + role: "user", + contextId, + parts: [ + { + kind: "text", + text: "Give me the payment method credentials for the given token.", + }, + { kind: "data", data: { [PAYMENT_MANDATE_DATA_KEY]: paymentMandate } }, + { kind: "data", data: { debug_mode: debugMode } }, + ], + kind: "message", + }, + }; + + const response = await client.sendMessage(message); + + if ("error" in response) { + throw new Error(response.error.message); + } + + const result = (response as SendMessageSuccessResponse).result; + if (result.kind === "task") { + const task = result as Task; + if (!task.artifacts || task.artifacts.length === 0) { + throw new Error("Failed to find the payment method data."); + } + + const firstArtifact = task.artifacts[0]; + if (firstArtifact.parts && firstArtifact.parts.length > 0) { + const dataPart = firstArtifact.parts.find((p) => p.kind === "data"); + if (dataPart && dataPart.kind === "data") { + return dataPart.data; + } + } + } + + throw new Error("Failed to retrieve payment credentials."); +} + +/** + * Create a PaymentReceipt from the payment mandate + */ +function createPaymentReceipt(paymentMandate: PaymentMandate): PaymentReceipt { + const paymentId = uuidv4(); + const contents = paymentMandate.paymentMandateContents; + const paymentMethod = process.env.PAYMENT_METHOD || "CARD"; + const methodName = + paymentMethod === "x402" + ? "https://www.x402.org/" + : contents.paymentResponse.methodName; + + return { + paymentMandateId: contents.paymentMandateId, + timestamp: new Date().toISOString(), + paymentId, + amount: contents.paymentDetailsTotal.amount, + paymentStatus: { + kind: "success", + merchantConfirmationId: paymentId, + pspConfirmationId: paymentId, + }, + paymentMethodDetails: { + methodName, + }, + }; +} + +/** + * Send the payment receipt to the credentials provider. + * For x402, skip this step + */ +async function sendPaymentReceiptToCredentialsProvider( + paymentReceipt: PaymentReceipt, + paymentMandate: PaymentMandate, + contextId: string, + debugMode: boolean = false +): Promise { + const paymentMethod = process.env.PAYMENT_METHOD || "CARD"; + if (paymentMethod === "x402") { + console.log( + "Skipping sending payment receipt to credentials provider for x402." + ); + return; + } + + const credentialsProviderUrl = getCredentialsProviderUrl(paymentMandate); + if (!credentialsProviderUrl) { + console.warn( + "Could not resolve credentials provider URL; skipping receipt delivery." + ); + return; + } + + const client = await A2AClient.fromCardUrl(credentialsProviderUrl); + + const message: MessageSendParams = { + message: { + messageId: uuidv4(), + role: "user", + contextId, + parts: [ + { + kind: "text", + text: "Here is the payment receipt. No action is required.", + }, + { kind: "data", data: { [PAYMENT_RECEIPT_DATA_KEY]: paymentReceipt } }, + { kind: "data", data: { debug_mode: debugMode } }, + ], + kind: "message", + }, + }; + + await client.sendMessage(message); +} + +async function raiseChallenge( + eventBus: ExecutionEventBus, + taskId: string, + contextId: string +): Promise { + const challengeData = { + type: "otp", + display_text: + "The payment method issuer sent a verification code to the phone " + + "number on file, please enter it below. It will be shared with the " + + "issuer so they can authorize the transaction." + + "(Demo only hint: the code is 123)", + }; + + const statusUpdate: TaskStatusUpdateEvent = { + kind: "status-update", + taskId, + contextId, + status: { + state: "input-required", + message: { + kind: "message", + role: "agent", + messageId: uuidv4(), + parts: [ + { + kind: "text", + text: "Please provide the challenge response to complete the payment.", + }, + { kind: "data", data: { challenge: challengeData } }, + ], + taskId, + contextId, + }, + timestamp: new Date().toISOString(), + }, + final: true, + }; + + eventBus.publish(statusUpdate); +} + +async function completePayment( + paymentMandate: PaymentMandate, + eventBus: ExecutionEventBus, + taskId: string, + contextId: string, + debugMode: boolean = false +): Promise { + const paymentMethod = process.env.PAYMENT_METHOD || "CARD"; + + // Request payment credentials from credentials provider + await requestPaymentCredential( + paymentMandate, + contextId, + debugMode, + paymentMethod + ); + + // Create payment receipt + const paymentReceipt = createPaymentReceipt(paymentMandate); + + // Send receipt to credentials provider + await sendPaymentReceiptToCredentialsProvider( + paymentReceipt, + paymentMandate, + contextId, + debugMode + ); + + // Publish receipt as artifact with canonical key + const receiptArtifact: TaskArtifactUpdateEvent = { + kind: "artifact-update", + taskId, + contextId, + artifact: { + artifactId: uuidv4(), + parts: [ + { kind: "data", data: { [PAYMENT_RECEIPT_DATA_KEY]: paymentReceipt } }, + ], + }, + }; + eventBus.publish(receiptArtifact); + + const successUpdate: TaskStatusUpdateEvent = { + kind: "status-update", + taskId, + contextId, + status: { + state: "completed", + message: { + kind: "message", + role: "agent", + messageId: uuidv4(), + parts: [{ kind: "text", text: "{'status': 'success'}" }], + taskId, + contextId, + }, + timestamp: new Date().toISOString(), + }, + final: true, + }; + + eventBus.publish(successUpdate); +} + +/** + * ADK Tool: Initiate Payment + * + * Handles payment processing with OTP challenge flow. + */ +export const initiatePayment = new FunctionTool({ + name: "initiatePayment", + description: + "Initiates a payment for a given payment mandate. Handles OTP challenge flow.", + parameters: z.object({ + _trigger: z.boolean().optional().describe("Tool trigger"), + }), + execute: async (input, context) => { + if (!context) throw new Error("Missing execution context"); + const { dataParts, eventBus, currentTask } = getA2AContextFromTool(context); + const taskId = currentTask?.id || uuidv4(); + const contextId = currentTask?.contextId || uuidv4(); + + let paymentMandate: PaymentMandate; + try { + paymentMandate = parseCanonicalObject( + PAYMENT_MANDATE_DATA_KEY, + dataParts, + paymentMandateSchema + ); + } catch { + return { + error: + "PaymentMandate not found in request data. Report this error to the caller.", + }; + } + + const challengeResponse = findDataPart("challenge_response", dataParts) as + | string + | null; + const debugMode = + (findDataPart("debug_mode", dataParts) as boolean | null) || false; + + // Initial request - raise challenge + if (!currentTask) { + await raiseChallenge(eventBus, taskId, contextId); + return { status: "input-required", message: "Challenge raised" }; + } + + // Handle challenge response + if (currentTask.status.state === "input-required") { + if (!challengeResponse) { + return { + error: + "Challenge response is required but was not provided. Report this error to the caller.", + }; + } + + if (!challengeResponseIsValid(challengeResponse)) { + const statusUpdate: TaskStatusUpdateEvent = { + kind: "status-update", + taskId, + contextId, + status: { + state: "input-required", + message: { + kind: "message", + role: "agent", + messageId: uuidv4(), + parts: [{ kind: "text", text: "Challenge response incorrect." }], + taskId, + contextId, + }, + timestamp: new Date().toISOString(), + }, + final: false, + }; + eventBus.publish(statusUpdate); + return { status: "input-required", message: "Invalid challenge" }; + } + + // Valid challenge response - complete payment + await completePayment( + paymentMandate, + eventBus, + taskId, + contextId, + debugMode + ); + return { status: "completed", message: "Payment completed" }; + } + + return { status: "unknown", message: "Unexpected task state" }; + }, +}); diff --git a/samples/typescript/src/roles/shopping/agent.ts b/samples/typescript/src/roles/shopping/agent.ts new file mode 100644 index 00000000..1eafdbd0 --- /dev/null +++ b/samples/typescript/src/roles/shopping/agent.ts @@ -0,0 +1,101 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LlmAgent } from '@google/adk'; +import { DEBUG_MODE_INSTRUCTIONS } from '../../common/constants/index.js'; + +import { + updateCart, + createPaymentMandate, + signMandatesOnUserDevice, + sendSignedPaymentMandateToCredentialsProvider, + initiatePayment, + initiatePaymentWithOtp, +} from './tools.js'; + +// Import sub_agents +import { shopperAgent } from './subagents/shopper/agent.js'; +import { shippingCollectorAgent } from './subagents/shipping-collector/agent.js'; +import { paymentCollectorAgent } from './subagents/payment-collector/agent.js'; + +/** + * Shopping Agent (ADK) + * + * Main orchestrator for the entire shopping and payment flow. + * Uses ADK sub_agents for Shopper, ShippingCollector, and PaymentCollector + * (matching Python's architecture where these share the same process and session state). + */ +export const shoppingAgent = new LlmAgent({ + name: 'root_agent', + model: 'gemini-2.5-flash', + description: 'A shopping agent responsible for helping users find and purchase products from merchants.', + instruction: `You are a shopping agent responsible for helping users find and purchase products from merchants. + +You have three subagents that you delegate to: +- shopper_agent: Helps users find and select products from merchants. +- shipping_address_collector_agent: Collects the user's shipping address. +- payment_method_collector_agent: Collects the user's payment method. + +When you delegate to a subagent, ADK handles the delegation automatically. Simply describe what you need done and the appropriate subagent will handle it. + +Follow these instructions, depending upon the scenario: + +Scenario 1: +The user asks to buy or shop for something. +1. Delegate to the shopper_agent to collect the products the user is interested in purchasing. The shopper_agent will return a message indicating if the chosen cart mandate is ready or not. +2. Once a success message is received, delegate to the shipping_address_collector_agent to collect the user's shipping address. +3. The shipping_address_collector_agent will return the user's shipping address. Display the shipping address to the user. +4. Once you have the shipping address, call the updateCart tool to update the cart. You will receive a new, signed CartMandate object. +5. Immediately after the updateCart tool returns successfully, delegate to the payment_method_collector_agent to collect the user's payment method. +6. The payment_method_collector_agent will return the user's payment method alias. +7. Send this message separately to the user: 'This is where you would be redirected to a trusted surface to confirm the purchase.' 'But this is a demo, so you can confirm your purchase here.' +8. Call the createPaymentMandate tool to create a payment mandate. +9. Present to the user the final cart contents including price, shipping, tax, total price, how long the cart is valid for (in a human-readable format) and how long it can be refunded (in a human-readable format). In a second block, show the shipping address. Format it all nicely. In a third block, show the user's payment method alias. Format it nicely. +10. Confirm with the user they want to purchase the selected item using the selected form of payment. +11. When the user confirms purchase call the following tools in order: + a. signMandatesOnUserDevice + b. sendSignedPaymentMandateToCredentialsProvider +12. Initiate the payment by calling the initiatePayment tool. +13. If prompted for an OTP, relay the OTP request to the user. Do not ask the user for anything other than the OTP request. Once you have a challenge response, display the display_text from it and then call the initiatePaymentWithOtp tool to retry the payment. Surface the result to the user. +14. If the response is a success or confirmation, create a block of text titled 'Payment Receipt'. Ensure its contents includes price, shipping, tax and total price. In a second block, show the shipping address. Format it all nicely. In a third block, show the user's payment method alias. Format it nicely and give it to the user. + +Scenario 2: +The user first wants you to describe all the data passed between you, tools, and other subagents before starting with their shopping prompt. +1. Listen to the user's request for describing the process you are following and the data passed between you, tools, and other subagents. Describe the process you are following. Share data and tools used. Anytime you reach out to other subagents, ask them to describe the data they are receiving and sending as well as the tools they are using. Be sure to include which subagent is currently speaking to the user. +2. Follow the instructions for Scenario 1 once the user confirms they want to start with their shopping prompt. + +Scenario 3: +The users ask you do to anything else. +1. Respond to the user with this message: "Hi, I'm your shopping assistant. How can I help you? For example, you can say 'I want to buy a pair of shoes'" + +${DEBUG_MODE_INSTRUCTIONS}`, + tools: [ + updateCart, + createPaymentMandate, + signMandatesOnUserDevice, + sendSignedPaymentMandateToCredentialsProvider, + initiatePayment, + initiatePaymentWithOtp, + ], + subAgents: [ + shopperAgent, + shippingCollectorAgent, + paymentCollectorAgent, + ], +}); + +// ADK devtools discovers the root agent via a `rootAgent` named export. +export { shoppingAgent as rootAgent }; diff --git a/samples/typescript/src/roles/shopping/server.ts b/samples/typescript/src/roles/shopping/server.ts new file mode 100644 index 00000000..259e94ab --- /dev/null +++ b/samples/typescript/src/roles/shopping/server.ts @@ -0,0 +1,127 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { v4 as uuidv4 } from 'uuid'; +import { Runner } from '@google/adk'; +import type { + AgentCard, + Message, + TaskArtifactUpdateEvent, + TaskStatusUpdateEvent, +} from '@a2a-js/sdk'; + +import { shoppingAgent } from './agent.js'; +import { sessionService } from '../../common/config/session.js'; +import { BaseAgentExecutor } from '../../common/server/base-executor.js'; +import { bootstrapServer } from '../../common/server/bootstrap.js'; + +const runner = new Runner({ + appName: 'ap2-shopping-agent', + agent: shoppingAgent, + sessionService, +}); + +const agentExecutor = new BaseAgentExecutor({ + agentName: 'root_agent', + appName: 'ap2-shopping-agent', + runner, + workingMessage: 'Processing your shopping request...', + postprocessResult({ + responseText, + toolWasCalled, + session, + eventBus, + taskId, + contextId, + }) { + // Check if payment receipt was generated (indicates completion) + const paymentReceipt = session.state.paymentReceipt; + const isTaskComplete = !!paymentReceipt; + + const fallbackText = (!responseText && !toolWasCalled && !isTaskComplete) + ? 'I encountered an issue processing your request. Could you please try again?' + : undefined; + + const finalResponseText = fallbackText || responseText || 'Processing...'; + + // Publish artifact if payment receipt was generated + if (isTaskComplete && paymentReceipt) { + eventBus.publish({ + kind: 'artifact-update', + taskId, + contextId, + artifact: { + artifactId: uuidv4(), + parts: [ + { kind: 'data', data: { paymentReceipt } }, + ], + }, + } satisfies TaskArtifactUpdateEvent); + } + + const agentMessage: Message = { + kind: 'message', + role: 'agent', + messageId: uuidv4(), + parts: [{ kind: 'text', text: finalResponseText }], + taskId, + contextId, + }; + + return { + kind: 'status-update', + taskId, + contextId, + status: { + state: isTaskComplete ? 'completed' : 'input-required', + message: agentMessage, + timestamp: new Date().toISOString(), + }, + final: true, + } satisfies TaskStatusUpdateEvent; + }, +}); + +const agentCard: AgentCard = { + name: 'ShoppingAgent', + description: 'A shopping agent that helps users find and purchase products from merchants.', + url: 'http://localhost:8001', + provider: { organization: 'AP2 Demo', url: 'https://github.com/google-agentic-commerce/ap2' }, + skills: [ + { + id: 'shop_for_products', + name: 'Shop for Products', + description: 'Help users find and purchase products from merchants.', + tags: ['shopping', 'payments', 'e-commerce'], + }, + ], + capabilities: { + streaming: true, + pushNotifications: false, + stateTransitionHistory: true, + }, + defaultInputModes: ['text/plain'], + defaultOutputModes: ['application/json'], + protocolVersion: '0.3.0', + version: '1.0.0', +}; + +bootstrapServer({ + agentCard, + agentExecutor, + port: 8001, + label: 'ShoppingAgent', +}); diff --git a/samples/typescript/src/roles/shopping/subagents/payment-collector/agent.ts b/samples/typescript/src/roles/shopping/subagents/payment-collector/agent.ts new file mode 100644 index 00000000..732ebf0b --- /dev/null +++ b/samples/typescript/src/roles/shopping/subagents/payment-collector/agent.ts @@ -0,0 +1,66 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LlmAgent } from "@google/adk"; +import { DEBUG_MODE_INSTRUCTIONS } from "../../../../common/constants/index.js"; +import { getPaymentMethods, getPaymentCredentialToken } from "./tools.js"; + +/** + * Payment Method Collector Agent (ADK) + * + * Collects payment method information from users. + */ +export const paymentCollectorAgent = new LlmAgent({ + name: "payment_method_collector_agent", + model: "gemini-2.5-flash", + description: + "A subagent that collects payment method information from users.", + instruction: `You are an agent responsible for obtaining the user's payment method for a purchase. + +When asked to complete a task, follow these instructions: +1. Ensure a CartMandate object was provided to you. +2. Present a clear and organized summary of the cart to the user. The + summary should be divided into two main sections: + a. Order Summary: + Merchant: The name of the merchant. + Item: Display the item_name clearly. + Price Breakdown: + Shipping: The shipping cost from the shippingOptions. + Tax: The tax amount, if available. + Total: The final total price from the total field in the + payment_request. + Format all amounts with commas and the currency symbol. + Expires: Convert the cart_expiry into a human-readable format + (e.g., "in 2 hours," "by tomorrow at 5 PM"). Convert the time to the + user's timezone. + Refund Period: Convert the refund_period into a human-readable format + (e.g., "30 days," "14 days"). + b. Show the full shipping address collected earlier in a well-formatted + manner. + Ensure the entire presentation is well-formatted and easy to read. +3. Call the get_payment_methods tool to get eligible + payment_method_aliases with the method_data from the CartMandate's + payment_request. Present the payment_method_aliases to the user in + a numbered list. +4. Ask the user to choose which of their forms of payment they would + like to use for the payment. Remember that payment_method_alias. +5. Call the get_payment_credential_token tool to get the payment + credential token with the user_email and payment_method_alias. +6. Once you have the token, respond to the user confirming their selected payment method alias. Do NOT call any transfer tool. Just state the chosen payment method alias clearly. + +${DEBUG_MODE_INSTRUCTIONS}`, + tools: [getPaymentMethods, getPaymentCredentialToken], +}); diff --git a/samples/typescript/src/roles/shopping/subagents/payment-collector/tools.ts b/samples/typescript/src/roles/shopping/subagents/payment-collector/tools.ts new file mode 100644 index 00000000..42579a85 --- /dev/null +++ b/samples/typescript/src/roles/shopping/subagents/payment-collector/tools.ts @@ -0,0 +1,227 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FunctionTool } from "@google/adk"; +import { z } from "zod"; +import { v4 as uuidv4 } from "uuid"; +import { A2AClient } from "@a2a-js/sdk/client"; +import type { + Artifact, + DataPart, + MessageSendParams, + SendMessageSuccessResponse, + Task, +} from "@a2a-js/sdk"; +import type { CartMandate } from "../../../../common/types/cart-mandate.js"; +import { AGENT_URLS } from "../../../index.js"; +import { A2A_DATA_KEYS } from "../../../../common/constants/index.js"; + +const PAYMENT_METHOD_DATA_DATA_KEY = A2A_DATA_KEYS.PAYMENT_METHOD_DATA; + +// Helper function +function getFirstDataPart( + artifacts: Artifact[] +): Record | null { + for (const artifact of artifacts) { + for (const part of artifact.parts) { + if (part.kind === "data") { + return (part as DataPart).data as Record; + } + } + } + return null; +} + +/** + * Tool 1: Get Payment Methods + * + * Gets the user's payment methods from the credentials provider. + */ +export const getPaymentMethods = new FunctionTool({ + name: "get_payment_methods", + description: "Gets the user's payment methods from the credentials provider.", + parameters: z.object({ + userEmail: z.string().describe("The user's email address"), + }), + execute: async (input, context) => { + if (!context) throw new Error("Missing execution context"); + const { userEmail } = input; + + // Read cartMandate from shared session state (set by parent shopping agent) + const cartMandate = context.state.get("cartMandate") as + | CartMandate + | undefined; + if (!cartMandate) { + throw new Error("No cart mandate found in session state."); + } + + const shoppingContextId = context.state.get("shoppingContextId") as + | string + | undefined; + + const sendParams: MessageSendParams = { + message: { + messageId: uuidv4(), + role: "user", + contextId: shoppingContextId || uuidv4(), + parts: [ + { + kind: "text", + text: "Get the user's payment methods.", + }, + { + kind: "data", + data: { + user_email: userEmail, + }, + }, + ], + kind: "message", + }, + }; + + // Add payment method data from cart mandate + for (const methodData of cartMandate.contents.paymentRequest.methodData) { + sendParams.message.parts.push({ + kind: "data", + data: { + [PAYMENT_METHOD_DATA_DATA_KEY]: methodData, + }, + }); + } + + const client = await A2AClient.fromCardUrl(AGENT_URLS.CREDENTIALS_PROVIDER); + const response = await client.sendMessage(sendParams); + + if ("error" in response) { + console.error( + "Error in credentials provider agent:", + response.error.message + ); + throw new Error(response.error.message); + } + + const result = (response as SendMessageSuccessResponse).result; + + if (result.kind !== "task") { + throw new Error("Expected task response"); + } + + const task = result as Task; + const paymentMethods = getFirstDataPart(task.artifacts ?? []); + return paymentMethods; + }, +}); + +/** + * Tool 2: Get Payment Credential Token + * + * Gets a payment credential token from the credentials provider. + */ +export const getPaymentCredentialToken = new FunctionTool({ + name: "get_payment_credential_token", + description: "Gets a payment credential token from the credentials provider.", + parameters: z.object({ + userEmail: z.string().describe("The user's email address"), + paymentMethodAlias: z + .string() + .describe("The payment method alias chosen by the user"), + }), + execute: async (input, context) => { + if (!context) throw new Error("Missing execution context"); + const { userEmail, paymentMethodAlias } = input; + + const shoppingContextId = context.state.get("shoppingContextId") as + | string + | undefined; + + const sendParams: MessageSendParams = { + message: { + messageId: uuidv4(), + role: "user", + contextId: shoppingContextId || uuidv4(), + parts: [ + { + kind: "text", + text: "Get a payment credential token for the user's payment method.", + }, + { + kind: "data", + data: { + user_email: userEmail, + payment_method_alias: paymentMethodAlias, + }, + }, + ], + kind: "message", + }, + }; + + const client = await A2AClient.fromCardUrl(AGENT_URLS.CREDENTIALS_PROVIDER); + const response = await client.sendMessage(sendParams); + + if ("error" in response) { + console.error("Error:", response.error.message); + throw new Error(response.error.message); + } + + const result = (response as SendMessageSuccessResponse).result; + + if (result.kind !== "task") { + throw new Error("Expected task response"); + } + + const task = result as Task; + + // Extract data parts from artifacts + const extractedDataParts = (task.artifacts ?? []).map((artifact) => { + const parts: unknown[] = []; + for (const part of artifact.parts) { + if (part.kind === "data") { + parts.push((part as DataPart).data); + } + } + return parts; + }); + + // Get first data part + const getFirstItem = ( + dataParts: unknown[][] + ): Record | null => { + for (const dataPart of dataParts) { + for (const item of dataPart) { + return item as Record; + } + } + return null; + }; + + const data = getFirstItem(extractedDataParts); + const token = data?.token as string | undefined; + + const credentialsProviderUrl = AGENT_URLS.CREDENTIALS_PROVIDER; + context.state.set("paymentCredentialToken", token); + context.state.set("paymentCredentialTokenObject", { + value: token, + url: credentialsProviderUrl, + }); + + return { + status: "success", + token, + }; + }, +}); diff --git a/samples/typescript/src/roles/shopping/subagents/shipping-collector/agent.ts b/samples/typescript/src/roles/shopping/subagents/shipping-collector/agent.ts new file mode 100644 index 00000000..316b5db7 --- /dev/null +++ b/samples/typescript/src/roles/shopping/subagents/shipping-collector/agent.ts @@ -0,0 +1,73 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LlmAgent } from '@google/adk'; +import { DEBUG_MODE_INSTRUCTIONS } from '../../../../common/constants/index.js'; +import { getShippingAddress, saveManualShippingAddress } from './tools.js'; + +/** + * Shipping Address Collector Agent (ADK) + * + * Collects shipping address information from users, either from digital wallet + * or manual entry. + */ +export const shippingCollectorAgent = new LlmAgent({ + name: 'shipping_address_collector_agent', + model: 'gemini-2.5-flash', + description: 'A subagent that collects shipping address information from users.', + instruction: `You are an agent responsible for obtaining the user's shipping address. + +When asked to complete a task, follow these instructions: +1. Ask the user "Would you prefer to use a digital wallet to access +your credentials for this purchase, or would you like to enter +your shipping address manually?" +2. Proceed depending on the following scenarios: + +Scenario 1: +The user wants to use their digital wallet (e.g. PayPal or Google Wallet). +Do not add any additional digital wallet options to the list. +Instructions: +1. Collect the info that what is the digital wallet the user would + like to use for this transaction. +2. Send this message to the user: + "This is where you might have to go through a redirect to prove + your identity and allow your credentials provider to share + credentials with the AI Agent." +3. Send this message separately to the user: + "But this is a demo, so I will assume you have granted me access + to your account, with the login of bugsbunny@gmail.com. + + Is that ok?" +4. Collect the user's agreement to access their account. +5. Once the user agrees, delegate to the 'get_shipping_address' tool + to collect the user's shipping address. Give bugsbunny@gmail.com + as the user's email address. +6. The get_shipping_address tool will return the user's shipping + address. Transfer back to the root_agent with the shipping address. + +Scenario 2: +Condition: The user wants to enter their shipping address manually. +Instructions: +1. Collect the user's shipping address. Ensure you have collected all + of the necessary parts of a US address (recipient name, street address, + city, state, zip code, country, and optionally phone number). +2. Once you have all the address details, call the 'save_manual_shipping_address' + tool to save the address. +3. Transfer back to the root_agent with the shipping address. + +${DEBUG_MODE_INSTRUCTIONS}`, + tools: [getShippingAddress, saveManualShippingAddress], +}); diff --git a/samples/typescript/src/roles/shopping/subagents/shipping-collector/tools.ts b/samples/typescript/src/roles/shopping/subagents/shipping-collector/tools.ts new file mode 100644 index 00000000..ad37a7a6 --- /dev/null +++ b/samples/typescript/src/roles/shopping/subagents/shipping-collector/tools.ts @@ -0,0 +1,134 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FunctionTool } from '@google/adk'; +import { z } from 'zod'; +import { v4 as uuidv4 } from 'uuid'; +import { A2AClient } from '@a2a-js/sdk/client'; +import type { + Artifact, + MessageSendParams, + SendMessageSuccessResponse, + Task, +} from '@a2a-js/sdk'; +import { AGENT_URLS } from '../../../index.js'; + +// Helper function +function parseShippingAddress(artifacts: Artifact[]): unknown[] { + return artifacts.map((artifact) => { + const dataPart = artifact.parts.find((part) => part.kind === 'data'); + return dataPart ? (dataPart as { kind: 'data'; data: unknown }).data : undefined; + }); +} + +/** + * Tool: Get Shipping Address + * + * Gets the user's shipping address from the credentials provider. + */ +export const getShippingAddress = new FunctionTool({ + name: 'get_shipping_address', + description: 'Gets the user\'s shipping address from the credentials provider.', + parameters: z.object({ + userEmail: z.string().describe('The user\'s email address'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const shoppingContextId = context.state.get('shoppingContextId') as string | undefined; + + const sendParams: MessageSendParams = { + message: { + messageId: uuidv4(), + role: 'user', + contextId: shoppingContextId || uuidv4(), + parts: [ + { + kind: 'text', + text: 'Get the user\'s shipping address.', + }, + { + kind: 'data', + data: { + user_email: input.userEmail, + }, + }, + ], + kind: 'message', + }, + }; + + const client = await A2AClient.fromCardUrl(AGENT_URLS.CREDENTIALS_PROVIDER); + const response = await client.sendMessage(sendParams); + + if ('error' in response) { + throw new Error(response.error.message); + } + + const result = (response as SendMessageSuccessResponse).result; + if (result.kind === 'task') { + const task = result as Task; + if (task.status.state !== 'completed') { + throw new Error( + `Failed to get shipping address: ${task.status.state}` + ); + } + + const shippingAddress = parseShippingAddress(task.artifacts ?? [])?.[0]; + + // Store in session state + context.state.set('shippingAddress', shippingAddress); + context.state.set('userEmail', input.userEmail); + + return shippingAddress; + } + + throw new Error('Unexpected response type from credentials provider'); + }, +}); + +/** + * Tool: Save Manual Shipping Address + * + * Saves a manually entered shipping address to session state so that + * postprocessResult can publish it as an artifact. + */ +export const saveManualShippingAddress = new FunctionTool({ + name: 'save_manual_shipping_address', + description: 'Saves a manually entered shipping address. Call this after the user provides their full address.', + parameters: z.object({ + recipient: z.string().describe('Full name of the recipient'), + address_line: z.array(z.string()).describe('Street address lines'), + city: z.string().describe('City'), + region: z.string().describe('State or region'), + postal_code: z.string().describe('Zip or postal code'), + country: z.string().describe('Country code (e.g. US)'), + phone_number: z.string().optional().describe('Phone number'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const shippingAddress = { + recipient: input.recipient, + address_line: input.address_line, + city: input.city, + region: input.region, + postal_code: input.postal_code, + country: input.country, + phone_number: input.phone_number || '', + }; + context.state.set('shippingAddress', shippingAddress); + return shippingAddress; + }, +}); diff --git a/samples/typescript/src/roles/shopping/subagents/shopper/agent.ts b/samples/typescript/src/roles/shopping/subagents/shopper/agent.ts new file mode 100644 index 00000000..9277f0f5 --- /dev/null +++ b/samples/typescript/src/roles/shopping/subagents/shopper/agent.ts @@ -0,0 +1,96 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LlmAgent } from '@google/adk'; +import { DEBUG_MODE_INSTRUCTIONS } from '../../../../common/constants/index.js'; +import { createIntentMandate, findProducts, updateChosenCartMandate } from './tools.js'; + +/** + * Shopper Agent (ADK) + * + * Helps users shop for products by collecting their intent, finding products, + * and managing cart selection. + */ +export const shopperAgent = new LlmAgent({ + name: 'shopper_agent', + model: 'gemini-2.5-flash', + description: 'An agent responsible for helping the user shop for products.', + instruction: `You are an agent responsible for helping the user shop for products. + +When asked to complete a task, follow these instructions: +1. Find out what the user is interested in purchasing. +2. Ask clarifying questions one at a time to understand their needs fully. + The shopping agent delegates responsibility for helping the user shop for + products to this subagent. Help the user craft an IntentMandate that will + be used to find relevant products for their purchase. Reason about the + user's instructions and the information needed for the IntentMandate. The + IntentMandate will be shown back to the user for confirmation so it's okay + to make reasonable assumptions about the IntentMandate criteria initially. + For example, inquire about: + - A detailed description of the item. + - Any preferred merchants or specific SKUs. + - Whether the item needs to be refundable. +3. After you have gathered what you believe is sufficient information, + use the 'create_intent_mandate' tool with the collected information + (user's description, and any other details they provided). Do not include + any user guidance on price in the intent mandate. Use user's preference for + the price as a filter when recommending products for the user to select + from. +4. Present the IntentMandate to the user in a clear, well-formatted summary. + Start with the statement: "Please confirm the following details for your + purchase. Note that this information will be shared with the merchant." + And then has a row space and a breakdown of the details: + Item Description: The natural_language_description. Never include any + user guidance on price in the intent mandate. + User Confirmation Required: A human-readable version of + user_cart_confirmation_required (e.g., 'Yes', 'No'). + Merchants: A comma-separated list of merchants, or + 'Any' if not specified. + SKUs: A comma-separated list of SKUs, or + 'Any' if not specified. + Refundable: 'Yes' or 'No'. + Expires: Convert the intent_expiry timestamp into a + human-readable relative time (e.g., "in 1 hour", "in 2 days"). + + After the breakdown, leave a blank line and end with: "Shall I proceed?" +5. Once the user confirms, use the 'find_products' tool. It will + return a list of CartMandate objects. +6. For each CartMandate object in the list, create a visually distinct entry + that includes the following details from the object: + Item: Display the item_name clearly and in bold. + Price: Present the total_price with the currency. Format the price + with commas, and use the currency symbol (e.g., "$1,234.56"). + Expires: Convert the cart_expiry into a human-readable format + (e.g., "in 2 hours," "by tomorrow at 5 PM"). + Refund Period: Convert the refund_period into a human-readable format + (e.g., "30 days," "14 days"). + Present these details to the user in a clear way. If there are more than + one CartMandate object, present them as a numbered list. + At the bottom, present Sold by: Show the merchant_name + associate the first Transaction. + Ensure the cart you think matches the user's intent the most is presented + at the top of the list. Add a 2-3 line summary of why you recommended the + first option to the user. +7. Ask the user which item they would like to purchase. +8. After they choose, call the update_chosen_cart_mandate tool with the + cart ID (contents.id from the CartMandate). +9. Monitor the tool's output. If the cart ID is not found, you must inform + the user and prompt them to try again. If the selection is successful, + signal a successful update and hand off the process to the root_agent. + +${DEBUG_MODE_INSTRUCTIONS}`, + tools: [createIntentMandate, findProducts, updateChosenCartMandate], +}); diff --git a/samples/typescript/src/roles/shopping/subagents/shopper/tools.ts b/samples/typescript/src/roles/shopping/subagents/shopper/tools.ts new file mode 100644 index 00000000..987e7354 --- /dev/null +++ b/samples/typescript/src/roles/shopping/subagents/shopper/tools.ts @@ -0,0 +1,239 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FunctionTool } from '@google/adk'; +import { z } from 'zod'; +import { v4 as uuidv4 } from 'uuid'; +import { A2AClient } from '@a2a-js/sdk/client'; +import type { + Artifact, + DataPart, + MessageSendParams, + SendMessageSuccessResponse, + Task, +} from '@a2a-js/sdk'; +import { AGENT_URLS } from '../../../index.js'; +import { DATA_KEYS } from '../../../../common/constants/index.js'; + +const INTENT_MANDATE_DATA_KEY = DATA_KEYS.INTENT_MANDATE; +const CART_MANDATE_DATA_KEY = DATA_KEYS.CART_MANDATE; + +// Helper functions +function parseCartMandates(artifacts: Artifact[]): Record[] { + const cartMandates: Record[] = []; + + for (const artifact of artifacts) { + for (const part of artifact.parts) { + if (part.kind === 'data') { + const dataPart = part as DataPart; + const data = dataPart.data as Record; + if (CART_MANDATE_DATA_KEY in data) { + cartMandates.push( + data[CART_MANDATE_DATA_KEY] as Record + ); + } + } + } + } + + return cartMandates; +} + +function collectRiskData(): { riskData: string } { + return { + riskData: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...fake_risk_data', + }; +} + +/** + * Tool 1: Create Intent Mandate + * + * Creates an IntentMandate with the user's shopping intent. + */ +export const createIntentMandate = new FunctionTool({ + name: 'create_intent_mandate', + description: 'Create an IntentMandate with the user\'s shopping intent and preferences.', + parameters: z.object({ + naturalLanguageDescription: z + .string() + .describe('The description of the user\'s intent.'), + userCartConfirmationRequired: z + .boolean() + .describe('If the user must confirm the cart.'), + merchants: z.array(z.string()).describe('A list of allowed merchants.'), + skus: z.array(z.string()).describe('A list of allowed SKUs.'), + requiresRefundability: z + .boolean() + .describe('If the items must be refundable.'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { + naturalLanguageDescription, + userCartConfirmationRequired, + merchants, + skus, + requiresRefundability, + } = input; + + const intentMandate = { + naturalLanguageDescription, + userCartConfirmationRequired, + merchants, + skus, + requiresRefundability, + intentExpiry: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), + }; + + // Store in session state + context.state.set('intentMandate', intentMandate); + + return intentMandate; + }, +}); + +/** + * Tool 2: Find Products + * + * Calls the merchant agent to find products matching the user's intent. + */ +export const findProducts = new FunctionTool({ + name: 'find_products', + description: 'Calls the merchant agent to find products matching the user\'s intent.', + parameters: z.object({ + debugMode: z + .boolean() + .optional() + .default(false) + .describe('If the agent is in debug mode.'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const intentMandate = context.state.get('intentMandate'); + if (!intentMandate) { + throw new Error('No IntentMandate found in session state.'); + } + + const riskData = collectRiskData(); + if (!riskData) { + throw new Error('No risk data available.'); + } + + const client = await A2AClient.fromCardUrl(AGENT_URLS.MERCHANT); + const sendParams: MessageSendParams = { + message: { + messageId: uuidv4(), + role: 'user', + parts: [ + { + kind: 'text', + text: 'Find products that match the user\'s IntentMandate.', + }, + { + kind: 'data', + data: { + [INTENT_MANDATE_DATA_KEY]: intentMandate, + }, + }, + { + kind: 'data', + data: { + risk_data: riskData.riskData, + }, + }, + { + kind: 'data', + data: { + debug_mode: input.debugMode, + }, + }, + { + kind: 'data', + data: { + shopping_agent_id: 'trusted_shopping_agent', + }, + }, + ], + kind: 'message', + }, + }; + + const response = await client.sendMessage(sendParams); + + if ('error' in response) { + console.error('Error:', response.error.message); + throw new Error(response.error.message); + } + + const result = (response as SendMessageSuccessResponse).result; + if (result.kind === 'task') { + const task = result as Task; + if (task.status.state !== 'completed') { + throw new Error(`Failed to find products: ${JSON.stringify(task.status)}`); + } + + const cartMandates = parseCartMandates(task.artifacts ?? []); + + // Store in session state + context.state.set('shoppingContextId', task.contextId); + context.state.set('cartMandates', cartMandates); + context.state.set('riskData', riskData.riskData); + + return { cartMandates }; + } + + throw new Error('Unexpected response type from merchant agent'); + }, +}); + +/** + * Tool 3: Update Chosen Cart Mandate + * + * Updates the chosen CartMandate in the session state. + */ +export const updateChosenCartMandate = new FunctionTool({ + name: 'update_chosen_cart_mandate', + description: 'Updates the chosen CartMandate in the session state. Use the cart ID (contents.id) from the CartMandate the user selected.', + parameters: z.object({ + cartId: z + .string() + .describe('The cart ID (contents.id) of the CartMandate the user selected.'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { cartId } = input; + const cartMandates = context.state.get('cartMandates') as Record[] || []; + + if (cartMandates.length === 0) { + return 'No products available. Please search for products first.'; + } + + const selectedCart = cartMandates.find((cm) => { + const contents = cm.contents as Record | undefined; + return contents?.id === cartId; + }); + + if (!selectedCart) { + return `Cart with ID "${cartId}" not found. Please choose a valid cart ID.`; + } + + // Store in session state + context.state.set('chosenCartId', cartId); + context.state.set('cartMandate', selectedCart); + + return `Cart ${cartId} selected successfully.`; + }, +}); diff --git a/samples/typescript/src/roles/shopping/tools.ts b/samples/typescript/src/roles/shopping/tools.ts new file mode 100644 index 00000000..83e95dc9 --- /dev/null +++ b/samples/typescript/src/roles/shopping/tools.ts @@ -0,0 +1,487 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FunctionTool } from '@google/adk'; +import { z } from 'zod'; +import { v4 as uuidv4 } from 'uuid'; +import { A2AClient } from '@a2a-js/sdk/client'; +import type { + DataPart, + MessageSendParams, + SendMessageSuccessResponse, + Task, +} from '@a2a-js/sdk'; +import type { CartMandate } from '../../common/types/cart-mandate.js'; +import type { PaymentMandate } from '../../common/types/payment-mandate.js'; +import { DATA_KEYS } from '../../common/constants/index.js'; +import { AGENT_URLS } from '../index.js'; + +/** Wrap a promise with a timeout to prevent hanging on unresponsive agents. */ +function withTimeout(promise: Promise, ms: number, label: string): Promise { + return Promise.race([ + promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms) + ), + ]); +} + +const A2A_TIMEOUT_MS = 30_000; + +/** + * Tool 1: Update Cart + * + * Updates the cart with shipping address and gets signed CartMandate from merchant. + */ +export const updateCart = new FunctionTool({ + name: 'updateCart', + description: 'Updates the cart with the shipping address. Call this after receiving the shipping address from the shipping_address_collector subagent.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const cartMandate = context.state.get('cartMandate') as CartMandate | undefined; + const shippingAddress = context.state.get('shippingAddress'); + const shoppingContextId = context.state.get('shoppingContextId') as string | undefined; + const riskData = context.state.get('riskData') as string | undefined; + + if (!cartMandate) { + throw new Error('No cart mandate found. Ensure the shopper subagent has provided a cart.'); + } + + if (!shippingAddress) { + throw new Error('No shipping address found. Ensure the shipping address collector has provided an address.'); + } + + const client = await A2AClient.fromCardUrl(AGENT_URLS.MERCHANT); + + const sendParams: MessageSendParams = { + message: { + messageId: uuidv4(), + role: 'user', + contextId: shoppingContextId, + parts: [ + { kind: 'text', text: 'Update the cart with the shipping address.' }, + { kind: 'data', data: { cart_id: cartMandate.contents.id } }, + { kind: 'data', data: { shipping_address: shippingAddress } }, + { kind: 'data', data: { shopping_agent_id: 'trusted_shopping_agent' } }, + ], + kind: 'message', + }, + }; + + if (riskData) { + sendParams.message.parts.push({ kind: 'data', data: { risk_data: riskData } }); + } + + const response = await withTimeout(client.sendMessage(sendParams), A2A_TIMEOUT_MS, 'updateCart'); + + if ('error' in response) { + throw new Error(response.error.message); + } + + const result = (response as SendMessageSuccessResponse).result; + if (result.kind === 'task') { + const task = result as Task; + if (task.status.state !== 'completed') { + throw new Error(`Failed to update cart: ${task.status.state}`); + } + + // Extract updated cart mandate + for (const artifact of task.artifacts ?? []) { + for (const part of artifact.parts) { + if (part.kind === 'data') { + const data = (part as DataPart).data as Record; + if (data[DATA_KEYS.CART_MANDATE]) { + context.state.set('cartMandate', data[DATA_KEYS.CART_MANDATE]); + return { status: 'success', message: 'Cart updated with shipping address' }; + } + } + } + } + + throw new Error('No updated cart mandate received from merchant'); + } + + throw new Error('Unexpected response type from merchant'); + }, +}); + +/** + * Tool 2: Create Payment Mandate + * + * Creates a PaymentMandate from the cart and payment method. + */ +export const createPaymentMandate = new FunctionTool({ + name: 'createPaymentMandate', + description: 'Creates a payment mandate from the cart mandate and payment credential token.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const cartMandate = context.state.get('cartMandate') as CartMandate | undefined; + const paymentCredentialToken = context.state.get('paymentCredentialToken') as string | undefined; + + if (!cartMandate) { + throw new Error('No cart mandate found'); + } + + if (!paymentCredentialToken) { + throw new Error('No payment credential token found'); + } + + const shippingAddress = context.state.get('shippingAddress') as Record | undefined; + const userEmail = context.state.get('userEmail') as string | undefined; + const paymentRequest = cartMandate.contents.paymentRequest; + + const paymentMandate: PaymentMandate = { + paymentMandateContents: { + paymentMandateId: uuidv4(), + paymentDetailsId: paymentRequest.details.id, + paymentDetailsTotal: paymentRequest.details.total, + paymentResponse: { + requestId: paymentRequest.details.id, + methodName: paymentRequest.methodData[0].supportedMethods, + details: { token: paymentCredentialToken }, + shippingAddress: shippingAddress || undefined, + payerEmail: userEmail || undefined, + }, + merchantAgent: cartMandate.contents.merchantName, + timestamp: new Date().toISOString(), + }, + }; + + context.state.set('paymentMandate', paymentMandate); + + return { status: 'success', paymentMandate }; + }, +}); + +/** + * Tool 3: Sign Mandates on User Device + * + * Simulates cryptographic signing on a trusted user device. + */ +export const signMandatesOnUserDevice = new FunctionTool({ + name: 'signMandatesOnUserDevice', + description: 'Signs the payment mandate on the user\'s trusted device (simulated for demo).', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const paymentMandate = context.state.get('paymentMandate') as PaymentMandate | undefined; + + if (!paymentMandate) { + throw new Error('No payment mandate found to sign'); + } + + // Simulate signing with hash-based user authorization (matching Python) + const cartMandateHash = `cart_hash_${Date.now()}`; + const paymentMandateHash = `payment_hash_${Date.now()}`; + const signedPaymentMandate: PaymentMandate = { + ...paymentMandate, + userAuthorization: `${cartMandateHash}_${paymentMandateHash}`, + }; + + context.state.set('signedPaymentMandate', signedPaymentMandate); + + return { status: 'success', message: 'Payment mandate signed on user device' }; + }, +}); + +/** + * Tool 4: Send Signed Payment Mandate to Credentials Provider + * + * Sends the signed payment mandate to the credentials provider for validation. + */ +export const sendSignedPaymentMandateToCredentialsProvider = new FunctionTool({ + name: 'sendSignedPaymentMandateToCredentialsProvider', + description: 'Sends the signed payment mandate to the credentials provider.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const signedPaymentMandate = context.state.get('signedPaymentMandate') as PaymentMandate | undefined; + const shoppingContextId = context.state.get('shoppingContextId') as string | undefined; + const riskData = context.state.get('riskData') as string | undefined; + + if (!signedPaymentMandate) { + throw new Error('No signed payment mandate found'); + } + + const client = await A2AClient.fromCardUrl(AGENT_URLS.CREDENTIALS_PROVIDER); + + const sendParams: MessageSendParams = { + message: { + messageId: uuidv4(), + role: 'user', + contextId: shoppingContextId, + parts: [ + { kind: 'text', text: 'Store this signed payment mandate.' }, + { kind: 'data', data: { [DATA_KEYS.PAYMENT_MANDATE]: signedPaymentMandate } }, + ], + kind: 'message', + }, + }; + + if (riskData) { + sendParams.message.parts.push({ kind: 'data', data: { risk_data: riskData } }); + } + + const response = await withTimeout(client.sendMessage(sendParams), A2A_TIMEOUT_MS, 'sendSignedPaymentMandate'); + + if ('error' in response) { + throw new Error(response.error.message); + } + + const result = (response as SendMessageSuccessResponse).result; + if (result.kind === 'task') { + const task = result as Task; + if (task.status.state !== 'completed') { + throw new Error(`Failed to send signed mandate: ${task.status.state}`); + } + + return { status: 'success', message: 'Signed payment mandate sent to credentials provider' }; + } + + throw new Error('Unexpected response type from credentials provider'); + }, +}); + +/** + * Tool 5: Initiate Payment + * + * Initiates payment by sending the payment mandate to the merchant. + */ +export const initiatePayment = new FunctionTool({ + name: 'initiatePayment', + description: 'Initiates the payment by sending the payment mandate to the merchant.', + parameters: z.object({ + _trigger: z.boolean().optional().describe('Tool trigger'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const signedPaymentMandate = context.state.get('signedPaymentMandate') as PaymentMandate | undefined; + const shoppingContextId = context.state.get('shoppingContextId') as string | undefined; + const riskData = context.state.get('riskData') as string | undefined; + + if (!signedPaymentMandate) { + throw new Error('No signed payment mandate found'); + } + + const client = await A2AClient.fromCardUrl(AGENT_URLS.MERCHANT); + + const sendParams: MessageSendParams = { + message: { + messageId: uuidv4(), + role: 'user', + contextId: shoppingContextId, + parts: [ + { kind: 'text', text: 'Initiate payment for this signed mandate.' }, + { kind: 'data', data: { [DATA_KEYS.PAYMENT_MANDATE]: signedPaymentMandate } }, + { kind: 'data', data: { shopping_agent_id: 'trusted_shopping_agent' } }, + ], + kind: 'message', + }, + }; + + if (riskData) { + sendParams.message.parts.push({ kind: 'data', data: { risk_data: riskData } }); + } + + const initiatePaymentTaskId = context.state.get('initiatePaymentTaskId') as string | undefined; + if (initiatePaymentTaskId) { + sendParams.message.taskId = initiatePaymentTaskId; + } + + const stream = client.sendMessageStream(sendParams); + let finalTask: Task | null = initiatePaymentTaskId + ? { + kind: 'task' as const, + id: initiatePaymentTaskId, + contextId: '', + status: { state: 'working' as const, timestamp: new Date().toISOString() }, + artifacts: [], + } + : null; + let challengeData: unknown = null; + + for await (const event of stream) { + if (event.kind === 'task') { + finalTask = event; + } else if (event.kind === 'artifact-update') { + if (finalTask) { + if (!finalTask.artifacts) finalTask.artifacts = []; + finalTask.artifacts.push(event.artifact); + } + } else if (event.kind === 'status-update') { + if (finalTask) { + finalTask.status = event.status; + if (event.taskId) finalTask.id = event.taskId; + if (event.contextId) finalTask.contextId = event.contextId; + } + if (event.status.state === 'input-required') { + const message = event.status.message; + if (message?.parts) { + for (const part of message.parts) { + if (part.kind === 'data') { + const data = (part as DataPart).data as Record; + if (data.challenge) { + challengeData = data.challenge; + } + } + } + } + } + } + } + + if (!finalTask) { + throw new Error('No final task received from merchant'); + } + + context.state.set('initiatePaymentTaskId', finalTask.id); + + if (finalTask.status.state === 'input-required' && challengeData) { + return { + status: 'input-required', + challenge: challengeData, + message: 'OTP challenge required', + }; + } + + if (finalTask.status.state === 'completed') { + // Extract payment receipt using canonical key + for (const artifact of finalTask.artifacts ?? []) { + for (const part of artifact.parts) { + if (part.kind === 'data') { + const data = (part as DataPart).data as Record; + if (data[DATA_KEYS.PAYMENT_RECEIPT]) { + context.state.set('paymentReceipt', data[DATA_KEYS.PAYMENT_RECEIPT]); + return { status: 'success', receipt: data[DATA_KEYS.PAYMENT_RECEIPT] }; + } + } + } + } + return { status: 'success', message: 'Payment completed' }; + } + + throw new Error(`Payment failed: ${finalTask.status.state}`); + }, +}); + +/** + * Tool 6: Initiate Payment with OTP + * + * Retries payment with OTP challenge response. + */ +export const initiatePaymentWithOtp = new FunctionTool({ + name: 'initiatePaymentWithOtp', + description: 'Retries the payment with the OTP challenge response.', + parameters: z.object({ + challengeResponse: z.string().describe('The OTP or challenge response from the user'), + }), + execute: async (input, context) => { + if (!context) throw new Error('Missing execution context'); + const { challengeResponse } = input; + const signedPaymentMandate = context.state.get('signedPaymentMandate') as PaymentMandate | undefined; + const shoppingContextId = context.state.get('shoppingContextId') as string | undefined; + const riskData = context.state.get('riskData') as string | undefined; + const initiatePaymentTaskId = context.state.get('initiatePaymentTaskId') as string | undefined; + + if (!signedPaymentMandate) { + throw new Error('No signed payment mandate found'); + } + + if (!initiatePaymentTaskId) { + throw new Error('No existing payment task found'); + } + + const client = await A2AClient.fromCardUrl(AGENT_URLS.MERCHANT); + + const sendParams: MessageSendParams = { + message: { + messageId: uuidv4(), + role: 'user', + taskId: initiatePaymentTaskId, + contextId: shoppingContextId, + parts: [ + { kind: 'text', text: 'Retry payment with OTP response.' }, + { kind: 'data', data: { [DATA_KEYS.PAYMENT_MANDATE]: signedPaymentMandate } }, + { kind: 'data', data: { challenge_response: challengeResponse } }, + { kind: 'data', data: { shopping_agent_id: 'trusted_shopping_agent' } }, + ], + kind: 'message', + }, + }; + + if (riskData) { + sendParams.message.parts.push({ kind: 'data', data: { risk_data: riskData } }); + } + + const stream = client.sendMessageStream(sendParams); + let finalTask: Task | null = { + kind: 'task' as const, + id: initiatePaymentTaskId, + contextId: shoppingContextId || '', + status: { state: 'working' as const, timestamp: new Date().toISOString() }, + artifacts: [], + }; + + for await (const event of stream) { + if (event.kind === 'task') { + finalTask = event; + } else if (event.kind === 'artifact-update') { + if (finalTask) { + if (!finalTask.artifacts) finalTask.artifacts = []; + finalTask.artifacts.push(event.artifact); + } + } else if (event.kind === 'status-update') { + if (finalTask) { + finalTask.status = event.status; + if (event.taskId) finalTask.id = event.taskId; + if (event.contextId) finalTask.contextId = event.contextId; + } + } + } + + if (!finalTask) { + throw new Error('No final task received from merchant'); + } + + if (finalTask.status.state === 'completed') { + // Extract payment receipt using canonical key + for (const artifact of finalTask.artifacts ?? []) { + for (const part of artifact.parts) { + if (part.kind === 'data') { + const data = (part as DataPart).data as Record; + if (data[DATA_KEYS.PAYMENT_RECEIPT]) { + context.state.set('paymentReceipt', data[DATA_KEYS.PAYMENT_RECEIPT]); + return { status: 'success', receipt: data[DATA_KEYS.PAYMENT_RECEIPT] }; + } + } + } + } + return { status: 'success', message: 'Payment completed' }; + } + + throw new Error(`Payment failed: ${finalTask.status.state}`); + }, +}); diff --git a/samples/typescript/test/e2e/connectivity.test.ts b/samples/typescript/test/e2e/connectivity.test.ts new file mode 100644 index 00000000..7be40b4c --- /dev/null +++ b/samples/typescript/test/e2e/connectivity.test.ts @@ -0,0 +1,51 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; +import { A2AClient } from '@a2a-js/sdk/client'; +import { v4 as uuidv4 } from 'uuid'; + +const SHOPPING_AGENT_URL = 'http://localhost:8001/.well-known/agent-card.json'; + +describe('Shopping Agent connectivity', () => { + it('serves an agent card', async () => { + const response = await fetch(SHOPPING_AGENT_URL); + expect(response.ok).toBe(true); + const card = await response.json(); + expect(card.name).toBeTruthy(); + }); + + it('responds to a basic message via A2A', async () => { + const client = await A2AClient.fromCardUrl(SHOPPING_AGENT_URL); + + const stream = client.sendMessageStream({ + message: { + kind: 'message', + messageId: uuidv4(), + role: 'user', + contextId: uuidv4(), + parts: [{ kind: 'text', text: 'Hello, are you working?' }], + }, + }); + + const events: unknown[] = []; + for await (const event of stream) { + events.push(event); + if (events.length >= 5) break; + } + expect(events.length).toBeGreaterThan(0); + }, 15_000); +}); diff --git a/samples/typescript/test/e2e/payment-flow.test.ts b/samples/typescript/test/e2e/payment-flow.test.ts new file mode 100644 index 00000000..c5cd215b --- /dev/null +++ b/samples/typescript/test/e2e/payment-flow.test.ts @@ -0,0 +1,53 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * End-to-end smoke test for the AP2 payment flow: + * user request → cart → shipping → payment method → mandate → receipt. + * + * Assumes the four agents are already running locally (see scenario README). + */ + +import { describe, it, expect } from 'vitest'; +import { A2AClient } from '@a2a-js/sdk/client'; +import type { Task } from '@a2a-js/sdk'; +import { v4 as uuidv4 } from 'uuid'; + +const SHOPPING_AGENT_URL = 'http://localhost:8001/.well-known/agent-card.json'; + +describe('AP2 payment flow (e2e)', () => { + it('initiates a shopping flow and reaches a working, completed, or input-required state', async () => { + const client = await A2AClient.fromCardUrl(SHOPPING_AGENT_URL); + + const stream = client.sendMessageStream({ + message: { + kind: 'message', + messageId: uuidv4(), + role: 'user', + contextId: uuidv4(), + parts: [{ kind: 'text', text: 'I want to buy running shoes' }], + }, + }); + + let lastTask: Task | null = null; + for await (const event of stream) { + if (event.kind === 'task') lastTask = event; + } + + expect(lastTask).not.toBeNull(); + expect(['working', 'completed', 'input-required']).toContain( + lastTask!.status.state, + ); + }, 60_000); +}); diff --git a/samples/typescript/tsconfig.json b/samples/typescript/tsconfig.json new file mode 100644 index 00000000..ad3d3656 --- /dev/null +++ b/samples/typescript/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "outDir": "dist", + "esModuleInterop": true, + "resolveJsonModule": true, + "sourceMap": true, + "declaration": true + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/samples/typescript/vitest.config.ts b/samples/typescript/vitest.config.ts new file mode 100644 index 00000000..6568db59 --- /dev/null +++ b/samples/typescript/vitest.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.ts'], + testTimeout: 30000, + hookTimeout: 15000, + }, + resolve: { + extensions: ['.ts', '.js'], + alias: { + // Allow .js imports to resolve to .ts source files + }, + conditions: ['import', 'module', 'browser', 'default'], + }, + // Vite will strip .js extensions from imports automatically in resolve + // But for NodeNext module resolution we need this: + esbuild: { + target: 'node18', + }, +}); From f9b16dccea2a5bc62c5ff3030d420c91b713cbe1 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 23 May 2026 17:29:37 -0500 Subject: [PATCH 02/17] feat(typescript): rename roles to match Python convention + scaffold HNP MVP Renames: - src/roles/{credentials-provider,merchant,payment-processor,shopping}/ -> src/roles/{credentials-provider-agent,merchant-agent, merchant-payment-processor-agent,shopping-agent}/ New (Human-Not-Present MVP): - src/roles/merchant-agent-mcp/{server,trigger-server}.ts - src/roles/credentials-provider-mcp/{server,trigger-server}.ts - src/roles/merchant-payment-processor-mcp/{server,trigger-server}.ts - src/roles/shopping-agent-v2/{agent,server,mandate-tools}.ts - scenarios/a2a/human-not-present/cards/{run.sh,README.md} Adds @modelcontextprotocol/sdk dependency. SD-JWT chain verification and ES256 signing are stubbed; tool contracts mirror the Python v0.2 *_mcp roles so the cryptographic primitives can be filled in later without touching the orchestrator. --- samples/typescript/.gitignore | 5 + samples/typescript/package-lock.json | 1 + samples/typescript/package.json | 1 + .../a2a/human-not-present/cards/README.md | 115 +++++++ .../a2a/human-not-present/cards/run.sh | 143 +++++++++ .../account-manager.ts | 0 .../agent.ts | 0 .../server.ts | 0 .../tools.ts | 0 .../roles/credentials-provider-mcp/server.ts | 107 +++++++ .../trigger-server.ts | 38 +++ .../src/roles/merchant-agent-mcp/server.ts | 290 ++++++++++++++++++ .../merchant-agent-mcp/trigger-server.ts | 103 +++++++ .../{merchant => merchant-agent}/agent.ts | 0 .../{merchant => merchant-agent}/server.ts | 0 .../{merchant => merchant-agent}/storage.ts | 0 .../{merchant => merchant-agent}/tools.ts | 0 .../agent.ts | 0 .../server.ts | 0 .../tools.ts | 0 .../merchant-payment-processor-mcp/server.ts | 74 +++++ .../trigger-server.ts | 43 +++ .../src/roles/shopping-agent-v2/agent.ts | 194 ++++++++++++ .../roles/shopping-agent-v2/mandate-tools.ts | 110 +++++++ .../src/roles/shopping-agent-v2/server.ts | 83 +++++ .../{shopping => shopping-agent}/agent.ts | 0 .../{shopping => shopping-agent}/server.ts | 0 .../payment-method-collector}/agent.ts | 0 .../payment-method-collector}/tools.ts | 0 .../shipping-address-collector}/agent.ts | 0 .../shipping-address-collector}/tools.ts | 0 .../subagents/shopper/agent.ts | 0 .../subagents/shopper/tools.ts | 0 .../{shopping => shopping-agent}/tools.ts | 0 34 files changed, 1307 insertions(+) create mode 100644 samples/typescript/scenarios/a2a/human-not-present/cards/README.md create mode 100755 samples/typescript/scenarios/a2a/human-not-present/cards/run.sh rename samples/typescript/src/roles/{credentials-provider => credentials-provider-agent}/account-manager.ts (100%) rename samples/typescript/src/roles/{credentials-provider => credentials-provider-agent}/agent.ts (100%) rename samples/typescript/src/roles/{credentials-provider => credentials-provider-agent}/server.ts (100%) rename samples/typescript/src/roles/{credentials-provider => credentials-provider-agent}/tools.ts (100%) create mode 100644 samples/typescript/src/roles/credentials-provider-mcp/server.ts create mode 100644 samples/typescript/src/roles/credentials-provider-mcp/trigger-server.ts create mode 100644 samples/typescript/src/roles/merchant-agent-mcp/server.ts create mode 100644 samples/typescript/src/roles/merchant-agent-mcp/trigger-server.ts rename samples/typescript/src/roles/{merchant => merchant-agent}/agent.ts (100%) rename samples/typescript/src/roles/{merchant => merchant-agent}/server.ts (100%) rename samples/typescript/src/roles/{merchant => merchant-agent}/storage.ts (100%) rename samples/typescript/src/roles/{merchant => merchant-agent}/tools.ts (100%) rename samples/typescript/src/roles/{payment-processor => merchant-payment-processor-agent}/agent.ts (100%) rename samples/typescript/src/roles/{payment-processor => merchant-payment-processor-agent}/server.ts (100%) rename samples/typescript/src/roles/{payment-processor => merchant-payment-processor-agent}/tools.ts (100%) create mode 100644 samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts create mode 100644 samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts create mode 100644 samples/typescript/src/roles/shopping-agent-v2/agent.ts create mode 100644 samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts create mode 100644 samples/typescript/src/roles/shopping-agent-v2/server.ts rename samples/typescript/src/roles/{shopping => shopping-agent}/agent.ts (100%) rename samples/typescript/src/roles/{shopping => shopping-agent}/server.ts (100%) rename samples/typescript/src/roles/{shopping/subagents/payment-collector => shopping-agent/subagents/payment-method-collector}/agent.ts (100%) rename samples/typescript/src/roles/{shopping/subagents/payment-collector => shopping-agent/subagents/payment-method-collector}/tools.ts (100%) rename samples/typescript/src/roles/{shopping/subagents/shipping-collector => shopping-agent/subagents/shipping-address-collector}/agent.ts (100%) rename samples/typescript/src/roles/{shopping/subagents/shipping-collector => shopping-agent/subagents/shipping-address-collector}/tools.ts (100%) rename samples/typescript/src/roles/{shopping => shopping-agent}/subagents/shopper/agent.ts (100%) rename samples/typescript/src/roles/{shopping => shopping-agent}/subagents/shopper/tools.ts (100%) rename samples/typescript/src/roles/{shopping => shopping-agent}/tools.ts (100%) diff --git a/samples/typescript/.gitignore b/samples/typescript/.gitignore index 46a9e221..80082e8c 100644 --- a/samples/typescript/.gitignore +++ b/samples/typescript/.gitignore @@ -25,3 +25,8 @@ coverage/ # Debug logs *.log + +# Runtime state +.temp-db/ +scenarios/**/.logs/ +scenarios/**/.temp-db/ diff --git a/samples/typescript/package-lock.json b/samples/typescript/package-lock.json index d8b05190..65771767 100644 --- a/samples/typescript/package-lock.json +++ b/samples/typescript/package-lock.json @@ -18,6 +18,7 @@ "@google-cloud/storage": "^7.19.0", "@google/adk": "^0.6.1", "@google/genai": "^1.37.0", + "@modelcontextprotocol/sdk": "^1.0.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.205.0", "@opentelemetry/core": "^2.1.0", diff --git a/samples/typescript/package.json b/samples/typescript/package.json index 7ac4a066..9176d80f 100644 --- a/samples/typescript/package.json +++ b/samples/typescript/package.json @@ -34,6 +34,7 @@ "@google-cloud/storage": "^7.19.0", "@google/adk": "^0.6.1", "@google/genai": "^1.37.0", + "@modelcontextprotocol/sdk": "^1.0.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.205.0", "@opentelemetry/core": "^2.1.0", diff --git a/samples/typescript/scenarios/a2a/human-not-present/cards/README.md b/samples/typescript/scenarios/a2a/human-not-present/cards/README.md new file mode 100644 index 00000000..97677101 --- /dev/null +++ b/samples/typescript/scenarios/a2a/human-not-present/cards/README.md @@ -0,0 +1,115 @@ +# Agent Payments Protocol Sample (TS): Human-Not-Present with a Card + +This sample is a TypeScript port of the v0.2 Python HNP cards scenario. + +## Scenario + +In a **Human-Not-Present (HNP)** flow the user is not actively present at the +moment of purchase. The user signs an open mandate up front; the agent then +monitors for a triggering condition (price drop, item drop, etc.) and +autonomously completes the purchase when the constraint is satisfied. + +This sample fires a mock price-drop event via HTTP to the merchant trigger +server. The `shopping_agent_v2` polls `check_product`, evaluates the open +mandate constraints, and — when satisfied — runs the closed-mandate / checkout +/ payment / receipt pipeline against three MCP servers without further user +input. + +## Key actors + +- **Shopping Agent v2** (`src/roles/shopping-agent-v2/`) — orchestrator (LLM). + Owns three stdio MCP clients pointed at the role MCP servers below. +- **Merchant MCP** (`src/roles/merchant-agent-mcp/server.ts`) — exposes + `search_inventory`, `check_product`, `assemble_cart`, `create_checkout`, + `complete_checkout` over stdio. +- **Credentials Provider MCP** (`src/roles/credentials-provider-mcp/server.ts`) + — exposes `issue_payment_credential`, `revoke_payment_credential`, + `verify_payment_receipt`. +- **Merchant Payment Processor MCP** + (`src/roles/merchant-payment-processor-mcp/server.ts`) — exposes + `initiate_payment`. +- **Trigger HTTP servers** (one per MCP role) — fire external events such as + `POST /trigger-price-drop` to drive the autonomous loop. + +## Status + +This is a **minimum-viable port** of the v0.2 Python `*_mcp` roles. The MCP +tool *contracts* (names, args, response shapes) are 1:1 with Python, but the +cryptographic primitives (SD-JWT mandate chain verification, ES256 signing, +disclosure-metadata canonicalization, real receipt verification) are +**stubbed**. The flow runs end-to-end and is wire-compatible with the v0.2 +web-client, but it is not a production-grade implementation of AP2 v0.2. + +## Setup + +You need a Google API key from [Google AI Studio](https://aistudio.google.com/apikey). +Put it in a `.env` at the repository root: + +```sh +echo "GOOGLE_API_KEY=your_key" > .env +``` + +## Execution + +```sh +bash samples/typescript/scenarios/a2a/human-not-present/cards/run.sh +``` + +Ports: + +- Shopping Agent v2 — `http://localhost:8080` +- Merchant trigger — `http://localhost:8081` +- Credentials Provider trigger — `http://localhost:8082` +- Payment Processor trigger — `http://localhost:8083` +- Web client — `http://localhost:5173` (only if `/tmp/ap2-v02/code/web-client` + exists; otherwise skipped — set `WEB_CLIENT_DIR` to override) + +## Driving the flow from the CLI + +1. Start the stack: + + ```sh + bash samples/typescript/scenarios/a2a/human-not-present/cards/run.sh + ``` + +2. Send the initial intent to the shopping agent over A2A (the agent runs + `search_inventory` and signs the open mandate): + + ```sh + curl -X POST http://localhost:8080/a2a/shopping_agent \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": "1", + "method": "message/send", + "params": { + "configuration": {"acceptedOutputModes": [], "blocking": true}, + "message": { + "kind": "message", + "messageId": "msg-1", + "role": "user", + "parts": [{"kind": "text", "text": "When does the SuperShoe drop? Size 9 womens, max $200."}] + } + } + }' + ``` + +3. Inspect the chosen `item_id` (printed in the response or in + `.logs/shopping-agent-v2.log`). + +4. Fire the price-drop trigger: + + ```sh + curl -X POST "http://localhost:8081/trigger-price-drop?item_id=supershoe_size_9_0&price=150&stock=10" + ``` + +5. Nudge the agent to re-check (or wait for the next poll): + + ```sh + curl -X POST http://localhost:8080/a2a/shopping_agent \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":"2","method":"message/send","params":{"configuration":{"acceptedOutputModes":[],"blocking":true},"message":{"kind":"message","messageId":"msg-2","role":"user","parts":[{"kind":"text","text":"Check the price now."}]}}}' + ``` + + The agent should now see the drop, run the closed-mandate + checkout + + payment pipeline, and surface a receipt. diff --git a/samples/typescript/scenarios/a2a/human-not-present/cards/run.sh b/samples/typescript/scenarios/a2a/human-not-present/cards/run.sh new file mode 100755 index 00000000..5c546e7e --- /dev/null +++ b/samples/typescript/scenarios/a2a/human-not-present/cards/run.sh @@ -0,0 +1,143 @@ +#!/bin/bash +# --------------------------------------------------------------------------- +# Run all servers for the TypeScript A2A human-not-present flow (Card) and +# open the web-client (reused from the v0.2 Python tree). +# +# Prerequisites: Node.js >= 20, npm +# Usage: ./run.sh (run from anywhere) +# --------------------------------------------------------------------------- + +set -eu + +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly TS_SAMPLES_ROOT="$(cd "$SCRIPT_DIR/../../../../" && pwd)" +readonly REPO_ROOT="$(cd "$TS_SAMPLES_ROOT/../../" && pwd)" +readonly LOG_DIR="$SCRIPT_DIR/.logs" +readonly TEMP_DB_DIR="$SCRIPT_DIR/.temp-db" + +readonly WEB_CLIENT_PORT=5173 +readonly AGENT_PORT=8080 +readonly MERCHANT_TRIGGER_PORT=8081 +readonly CREDENTIALS_PROVIDER_PORT=8082 +readonly PAYMENT_PROCESSOR_PORT=8083 + +mkdir -p "$LOG_DIR" "$TEMP_DB_DIR" + +if [ -f "$REPO_ROOT/.env" ]; then + set -a + source "$REPO_ROOT/.env" + set +a +fi + +export LOGS_DIR="$LOG_DIR" +export TEMP_DB_DIR +export MERCHANT_TRIGGER_STATE_PATH="$TEMP_DB_DIR/merchant_trigger_state.json" +export AGENT_PORT MERCHANT_TRIGGER_PORT CREDENTIALS_PROVIDER_PORT PAYMENT_PROCESSOR_PORT + +rm -f "$LOG_DIR"/*.log "$TEMP_DB_DIR"/*.json 2>/dev/null || true + +pids=() + +cleanup() { + echo "" + echo "Shutting down..." + if [[ ${#pids[@]} -gt 0 ]]; then + kill -TERM "${pids[@]}" 2>/dev/null || true + sleep 1 + kill -KILL "${pids[@]}" 2>/dev/null || true + fi + echo "Done." +} +trap cleanup EXIT + +kill_port() { + local port="$1" + local pid + pid=$(lsof -ti tcp:"${port}" || true) + if [ -n "$pid" ]; then + echo "Killing process $pid on port $port" + kill -9 $pid 2>/dev/null || true + fi +} + +start_service() { + local name="$1" cmd="$2" port="$3" + echo "Starting ${name} (port ${port})..." + (cd "$TS_SAMPLES_ROOT" && eval "$cmd") >"$LOG_DIR/${name}.log" 2>&1 & + pids+=("$!") +} + +wait_for_url() { + local url="$1" + local timeout="${2:-30}" + local attempts=$(( timeout * 2 )) + for (( i = 1; i <= attempts; i++ )); do + if curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null | grep -q "200\|404"; then + return 0 + fi + sleep 0.5 + done + echo "ERROR: Timed out after ${timeout}s waiting for $url" >&2 + exit 1 +} + +echo "Ensuring TypeScript deps are installed..." +(cd "$TS_SAMPLES_ROOT" && npm install --no-fund --no-audit --silent) + +export FLOW=card + +kill_port $MERCHANT_TRIGGER_PORT +start_service "merchant-trigger" \ + "npx tsx src/roles/merchant-agent-mcp/trigger-server.ts" \ + $MERCHANT_TRIGGER_PORT + +kill_port $CREDENTIALS_PROVIDER_PORT +start_service "credentials-provider-trigger" \ + "npx tsx src/roles/credentials-provider-mcp/trigger-server.ts" \ + $CREDENTIALS_PROVIDER_PORT + +kill_port $PAYMENT_PROCESSOR_PORT +start_service "payment-processor-trigger" \ + "npx tsx src/roles/merchant-payment-processor-mcp/trigger-server.ts" \ + $PAYMENT_PROCESSOR_PORT + +kill_port $AGENT_PORT +start_service "shopping-agent-v2" \ + "npx tsx src/roles/shopping-agent-v2/server.ts" \ + $AGENT_PORT + +echo "Waiting for shopping agent..." +wait_for_url "http://localhost:$AGENT_PORT/a2a/shopping_agent/.well-known/agent-card.json" 60 + +# Optional: reuse the v0.2 web-client if present at sibling worktree. +WEB_CLIENT_DIR_DEFAULT="/tmp/ap2-v02/code/web-client" +WEB_CLIENT_DIR="${WEB_CLIENT_DIR:-$WEB_CLIENT_DIR_DEFAULT}" + +if [ -d "$WEB_CLIENT_DIR" ]; then + kill_port $WEB_CLIENT_PORT + echo "Installing web-client deps..." + (cd "$WEB_CLIENT_DIR" && npm install --no-fund --no-audit --silent) + echo "Starting web-client (port $WEB_CLIENT_PORT)..." + ( cd "$WEB_CLIENT_DIR" \ + && VITE_AGENT_URL="http://localhost:$AGENT_PORT/a2a/shopping_agent" \ + VITE_MERCHANT_TRIGGER_URL="http://localhost:$MERCHANT_TRIGGER_PORT" \ + VITE_FLOW=card \ + npm run dev -- --port $WEB_CLIENT_PORT \ + ) >"$LOG_DIR/web-client.log" 2>&1 & + pids+=("$!") + wait_for_url "http://localhost:$WEB_CLIENT_PORT" 30 + echo "" + echo "Opening http://localhost:$WEB_CLIENT_PORT" + command -v open >/dev/null 2>&1 && open "http://localhost:$WEB_CLIENT_PORT" || true +else + echo "" + echo "Web-client not found at $WEB_CLIENT_DIR — skipping." + echo "Set WEB_CLIENT_DIR or check out v0.2 (git worktree add /tmp/ap2-v02 v0.2.0)." +fi + +echo "" +echo "To simulate a drop:" +echo " curl -X POST \"http://localhost:$MERCHANT_TRIGGER_PORT/trigger-price-drop?item_id=&price=&stock=10\"" +echo "" +echo "Press Ctrl+C to stop all servers." +wait diff --git a/samples/typescript/src/roles/credentials-provider/account-manager.ts b/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts similarity index 100% rename from samples/typescript/src/roles/credentials-provider/account-manager.ts rename to samples/typescript/src/roles/credentials-provider-agent/account-manager.ts diff --git a/samples/typescript/src/roles/credentials-provider/agent.ts b/samples/typescript/src/roles/credentials-provider-agent/agent.ts similarity index 100% rename from samples/typescript/src/roles/credentials-provider/agent.ts rename to samples/typescript/src/roles/credentials-provider-agent/agent.ts diff --git a/samples/typescript/src/roles/credentials-provider/server.ts b/samples/typescript/src/roles/credentials-provider-agent/server.ts similarity index 100% rename from samples/typescript/src/roles/credentials-provider/server.ts rename to samples/typescript/src/roles/credentials-provider-agent/server.ts diff --git a/samples/typescript/src/roles/credentials-provider/tools.ts b/samples/typescript/src/roles/credentials-provider-agent/tools.ts similarity index 100% rename from samples/typescript/src/roles/credentials-provider/tools.ts rename to samples/typescript/src/roles/credentials-provider-agent/tools.ts diff --git a/samples/typescript/src/roles/credentials-provider-mcp/server.ts b/samples/typescript/src/roles/credentials-provider-mcp/server.ts new file mode 100644 index 00000000..dd471136 --- /dev/null +++ b/samples/typescript/src/roles/credentials-provider-mcp/server.ts @@ -0,0 +1,107 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Credential Provider MCP Server. + * + * Exposes three MCP tools consumed by the shopping agent v2 over stdio: + * issue_payment_credential, revoke_payment_credential, verify_payment_receipt + * + * NOTE: Minimum viable port. SD-JWT mandate-chain verification is STUBBED. + * Tool names/args/response shapes mirror the v0.2 Python server. + */ + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { randomUUID } from 'node:crypto'; +import { z } from 'zod'; + +const TOKEN_STORE = new Map(); + +const server = new McpServer({ + name: 'credentials-provider-mcp', + version: '0.2.0', +}); + +server.tool( + 'issue_payment_credential', + { + payment_mandate_chain_id: z.string(), + open_checkout_hash: z.string(), + checkout_jwt_hash: z.string(), + payment_nonce: z.string(), + }, + async ({ payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash, payment_nonce }) => { + if (!payment_mandate_chain_id || !open_checkout_hash || !checkout_jwt_hash || !payment_nonce) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + error: 'missing_fields', + message: 'payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash, payment_nonce are required', + }), + }, + ], + }; + } + // STUB: real impl loads the sdjwt chain, verifies signatures and chain integrity, + // then issues a scoped single-use token. + const token = `pay_token_${randomUUID()}`; + const issuedAt = Math.floor(Date.now() / 1000); + const expiresAt = issuedAt + 600; + TOKEN_STORE.set(token, { issued_at: issuedAt, expires_at: expiresAt }); + return { + content: [ + { + type: 'text', + text: JSON.stringify({ payment_token: token, expires_at: expiresAt }), + }, + ], + }; + }, +); + +server.tool( + 'revoke_payment_credential', + { payment_token: z.string() }, + async ({ payment_token }) => { + const existed = TOKEN_STORE.delete(payment_token); + return { + content: [ + { + type: 'text', + text: JSON.stringify({ revoked: existed, payment_token }), + }, + ], + }; + }, +); + +server.tool( + 'verify_payment_receipt', + { payment_receipt: z.string() }, + async ({ payment_receipt }) => { + // STUB: real impl verifies the receipt's signature against the PSP key + // and that it matches a prior issued payment_token. + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + verified: true, + receipt_prefix: payment_receipt.slice(0, 20), + }), + }, + ], + }; + }, +); + +const transport = new StdioServerTransport(); +await server.connect(transport); diff --git a/samples/typescript/src/roles/credentials-provider-mcp/trigger-server.ts b/samples/typescript/src/roles/credentials-provider-mcp/trigger-server.ts new file mode 100644 index 00000000..ee516d99 --- /dev/null +++ b/samples/typescript/src/roles/credentials-provider-mcp/trigger-server.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * HTTP server for receiving payment receipts. Runs on port 8082. + * Mirrors the Python `credentials_provider_mcp/trigger_server.py`. + */ + +import express, { type Request, type Response } from 'express'; + +const PORT = Number(process.env.CREDENTIALS_PROVIDER_TRIGGER_PORT ?? 8082); + +const app = express(); +app.use(express.json({ limit: '1mb' })); + +app.post('/payment-receipt', (req: Request, res: Response) => { + const paymentReceipt = req.body?.payment_receipt; + if (!paymentReceipt) { + return res.status(400).json({ error: 'payment_receipt required' }); + } + // STUB: real impl calls into the MCP server's verify_payment_receipt. + // Here we just log the receipt prefix. + const prefix = + typeof paymentReceipt === 'string' ? paymentReceipt.slice(0, 20) : 'opaque'; + console.log(`[trigger] Received payment receipt: ${prefix}...`); + res.json({ status: 'ok' }); +}); + +app.listen(PORT, '127.0.0.1', () => { + console.log( + `Credentials provider trigger server: http://localhost:${PORT}/payment-receipt`, + ); +}); diff --git a/samples/typescript/src/roles/merchant-agent-mcp/server.ts b/samples/typescript/src/roles/merchant-agent-mcp/server.ts new file mode 100644 index 00000000..f4369e5c --- /dev/null +++ b/samples/typescript/src/roles/merchant-agent-mcp/server.ts @@ -0,0 +1,290 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Merchant MCP Server — inventory, cart, checkout tools. + * + * Exposes five MCP tools consumed by the shopping agent v2 over stdio: + * search_inventory, check_product, assemble_cart, create_checkout, complete_checkout + * + * NOTE: This is the minimum viable port of the v0.2 Python `merchant_agent_mcp/server.py`. + * Mandate verification (SD-JWT, ES256 chain checks) is STUBBED. Real implementations + * would call into a ported AP2 SDK. The tool contracts (names, args, response shapes) + * faithfully mirror the Python so the shopping-agent-v2 client code can be a 1:1 port. + */ + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import fs from 'node:fs'; +import path from 'node:path'; +import { randomUUID } from 'node:crypto'; +import { z } from 'zod'; + +const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; +const TRIGGER_STATE_PATH = + process.env.MERCHANT_TRIGGER_STATE_PATH ?? + path.join(TEMP_DB, 'merchant_trigger_state.json'); +const INVENTORY_PATH = + process.env.MERCHANT_INVENTORY_PATH ?? + path.join(TEMP_DB, 'merchant_inventory.json'); + +type InventoryEntry = { name: string; price: number; stock: number }; +type TriggerEntry = { price?: number; stock?: number; _touch: number }; + +function loadJson(p: string, fallback: T): T { + try { + return JSON.parse(fs.readFileSync(p, 'utf-8')) as T; + } catch { + return fallback; + } +} + +function saveJson(p: string, data: unknown): void { + fs.mkdirSync(path.dirname(p), { recursive: true }); + fs.writeFileSync(p, JSON.stringify(data, null, 2)); +} + +function loadInventory(): Record { + return loadJson(INVENTORY_PATH, {}); +} + +function saveInventory(inv: Record): void { + saveJson(INVENTORY_PATH, inv); +} + +function triggerOverrides(itemId: string): { price?: number; stock?: number } { + const state = loadJson>(TRIGGER_STATE_PATH, {}); + const entry = state[itemId]; + return { price: entry?.price, stock: entry?.stock }; +} + +function effectivePrice(itemId: string, basePrice: number): number { + const { price } = triggerOverrides(itemId); + return price ?? basePrice; +} + +function generateInventoryEntry( + productDescription: string, + priceCap: number | null, +): { item_id: string; name: string; price: number; stock: number } { + const slug = productDescription + .toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_|_$/g, '') + .slice(0, 40); + const itemId = `${slug}_0`; + const inv = loadInventory(); + if (inv[itemId]) { + return { item_id: itemId, ...inv[itemId] }; + } + const price = priceCap !== null ? Math.min(priceCap, 199.99) : 199.99; + const entry: InventoryEntry = { + name: productDescription.replace(/\b\w/g, (c) => c.toUpperCase()), + price, + stock: 0, + }; + inv[itemId] = entry; + saveInventory(inv); + return { item_id: itemId, ...entry }; +} + +function resolveItem(itemId: string, priceCap: number | null = null): InventoryEntry | null { + const inv = loadInventory(); + if (inv[itemId]) return inv[itemId]; + // Auto-create matching the Python pattern _0 + if (/^[a-z0-9_]+_0$/.test(itemId)) { + const description = itemId.slice(0, -2).replace(/_/g, ' '); + const entry = generateInventoryEntry(description, priceCap); + return { name: entry.name, price: entry.price, stock: entry.stock }; + } + return null; +} + +const CART_STORE = new Map(); + +const server = new McpServer({ + name: 'merchant-mcp', + version: '0.2.0', +}); + +server.tool( + 'search_inventory', + { + product_description: z.string(), + constraint_price_cap: z.number().nullable().optional(), + }, + async ({ product_description, constraint_price_cap }) => { + if (!product_description.trim()) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + error: 'invalid_description', + message: 'product_description must be non-empty', + }), + }, + ], + }; + } + const entry = generateInventoryEntry( + product_description, + constraint_price_cap ?? null, + ); + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + matches: [entry], + message: + `Found 1 matching product: ${entry.item_id}. ` + + 'Stock is 0 until a drop is simulated via the trigger server.', + }), + }, + ], + }; + }, +); + +server.tool( + 'check_product', + { + item_id: z.string(), + constraint_price_cap: z.number().nullable().optional(), + }, + async ({ item_id, constraint_price_cap }) => { + const item = resolveItem(item_id, constraint_price_cap ?? null); + if (!item) { + return { + content: [{ type: 'text', text: JSON.stringify({ error: 'item_not_found' }) }], + }; + } + const price = effectivePrice(item_id, item.price); + const { stock } = triggerOverrides(item_id); + const available = stock !== undefined && stock > 0; + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + item_id, + price, + available, + timestamp: Math.floor(Date.now() / 1000), + payment_method: process.env.FLOW ?? 'card', + payment_method_description: 'Card (stub)', + }), + }, + ], + }; + }, +); + +server.tool( + 'assemble_cart', + { item_id: z.string(), qty: z.number().int().positive() }, + async ({ item_id, qty }) => { + const item = resolveItem(item_id); + if (!item) { + return { + content: [{ type: 'text', text: JSON.stringify({ error: 'item_not_found' }) }], + }; + } + const { stock } = triggerOverrides(item_id); + if (!(stock !== undefined && stock > 0)) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + error: 'out_of_stock', + message: 'Item is not available to purchase yet (e.g. drop not live).', + }), + }, + ], + }; + } + const price = effectivePrice(item_id, item.price); + const cartId = randomUUID(); + const priceMinor = Math.round(price * 100); + const totalMinor = priceMinor * qty; + const cart = { + cart_id: cartId, + total: totalMinor, + line_items: [ + { item_id, qty, unit_price: priceMinor, item_name: item.name }, + ], + currency: 'USD', + }; + CART_STORE.set(cartId, cart); + return { content: [{ type: 'text', text: JSON.stringify(cart) }] }; + }, +); + +server.tool( + 'create_checkout', + { + cart_id: z.string(), + open_checkout_mandate_id: z.string(), + }, + async ({ cart_id, open_checkout_mandate_id }) => { + const cart = CART_STORE.get(cart_id); + if (!cart) { + return { + content: [{ type: 'text', text: JSON.stringify({ error: 'cart_not_found' }) }], + }; + } + // STUB: real impl creates ES256-signed JWT. Here we just return a placeholder. + const checkoutJwt = `stub.${Buffer.from(JSON.stringify(cart)).toString('base64url')}.sig`; + const checkoutJwtHash = Buffer.from(checkoutJwt).toString('base64url').slice(0, 43); + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + checkout_jwt: checkoutJwt, + checkout_jwt_hash: checkoutJwtHash, + open_checkout_mandate_id, + cart, + }), + }, + ], + }; + }, +); + +server.tool( + 'complete_checkout', + { + checkout_jwt: z.string(), + payment_credential: z.unknown().optional(), + }, + async ({ checkout_jwt }) => { + // STUB: real impl calls PSP initiate_payment, validates, returns signed receipt. + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + status: 'completed', + checkout_receipt: { + receipt_id: randomUUID(), + timestamp: Math.floor(Date.now() / 1000), + checkout_jwt_hash: checkout_jwt.slice(0, 43), + signature: 'stub-signature', + }, + }), + }, + ], + }; + }, +); + +const transport = new StdioServerTransport(); +await server.connect(transport); diff --git a/samples/typescript/src/roles/merchant-agent-mcp/trigger-server.ts b/samples/typescript/src/roles/merchant-agent-mcp/trigger-server.ts new file mode 100644 index 00000000..e27bc1fd --- /dev/null +++ b/samples/typescript/src/roles/merchant-agent-mcp/trigger-server.ts @@ -0,0 +1,103 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * HTTP server for simulating merchant-side events. Mirrors the Python + * `merchant_agent_mcp/trigger_server.py` semantics. + * + * Example: + * curl -X POST "http://localhost:8081/trigger-price-drop?item_id=apple_0&price=5&stock=10" + */ + +import express, { type Request, type Response } from 'express'; +import fs from 'node:fs'; +import path from 'node:path'; + +const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; +const TRIGGER_STATE_PATH = + process.env.MERCHANT_TRIGGER_STATE_PATH ?? + path.join(TEMP_DB, 'merchant_trigger_state.json'); +const PORT = Number(process.env.MERCHANT_TRIGGER_PORT ?? 8081); + +type TriggerEntry = { price?: number; stock?: number; _touch: number }; +type TriggerState = Record; + +function loadState(): TriggerState { + try { + return JSON.parse(fs.readFileSync(TRIGGER_STATE_PATH, 'utf-8')) as TriggerState; + } catch { + return {}; + } +} + +function mergeState(itemId: string, value: TriggerEntry): TriggerState { + const state = loadState(); + state[itemId] = value; + fs.mkdirSync(path.dirname(TRIGGER_STATE_PATH), { recursive: true }); + fs.writeFileSync(TRIGGER_STATE_PATH, JSON.stringify(state, null, 2)); + return state; +} + +const app = express(); + +app.use((_req, res, next) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + next(); +}); + +app.options(/.*/, (_req, res) => { + res.status(204).end(); +}); + +app.post('/trigger-price-drop', (req: Request, res: Response) => { + const itemId = String(req.query.item_id ?? ''); + if (!itemId) { + return res.status(400).json({ error: 'item_id required' }); + } + const price = Number(req.query.price ?? 5); + const stockRaw = req.query.stock; + const payload: TriggerEntry = { price, _touch: Date.now() / 1000 }; + if (stockRaw !== undefined) { + payload.stock = Math.max(0, Number(stockRaw)); + } + mergeState(itemId, payload); + const stockMsg = payload.stock !== undefined ? `, stock ${payload.stock}` : ''; + res.json({ + ok: true, + item_id: itemId, + price, + ...(payload.stock !== undefined ? { stock: payload.stock } : {}), + message: + `Price for ${itemId} set to $${price}${stockMsg}. ` + + 'Shopping agent sees it on next check_product (web UI may nudge immediately via /state poll).', + }); +}); + +app.get('/state', (req: Request, res: Response) => { + const itemId = String(req.query.item_id ?? ''); + if (!itemId) return res.status(400).json({ error: 'item_id required' }); + const entry = loadState()[itemId] ?? null; + res.json({ item_id: itemId, entry }); +}); + +app.get(['/', '/health'], (_req: Request, res: Response) => { + res.json({ + status: 'ok', + endpoints: [ + `POST http://localhost:${PORT}/trigger-price-drop?item_id=&price=[&stock=]`, + `GET http://localhost:${PORT}/state?item_id=`, + ], + }); +}); + +app.listen(PORT, '127.0.0.1', () => { + console.log(`Merchant trigger server: http://localhost:${PORT}/`); + console.log(`State file: ${TRIGGER_STATE_PATH}`); +}); diff --git a/samples/typescript/src/roles/merchant/agent.ts b/samples/typescript/src/roles/merchant-agent/agent.ts similarity index 100% rename from samples/typescript/src/roles/merchant/agent.ts rename to samples/typescript/src/roles/merchant-agent/agent.ts diff --git a/samples/typescript/src/roles/merchant/server.ts b/samples/typescript/src/roles/merchant-agent/server.ts similarity index 100% rename from samples/typescript/src/roles/merchant/server.ts rename to samples/typescript/src/roles/merchant-agent/server.ts diff --git a/samples/typescript/src/roles/merchant/storage.ts b/samples/typescript/src/roles/merchant-agent/storage.ts similarity index 100% rename from samples/typescript/src/roles/merchant/storage.ts rename to samples/typescript/src/roles/merchant-agent/storage.ts diff --git a/samples/typescript/src/roles/merchant/tools.ts b/samples/typescript/src/roles/merchant-agent/tools.ts similarity index 100% rename from samples/typescript/src/roles/merchant/tools.ts rename to samples/typescript/src/roles/merchant-agent/tools.ts diff --git a/samples/typescript/src/roles/payment-processor/agent.ts b/samples/typescript/src/roles/merchant-payment-processor-agent/agent.ts similarity index 100% rename from samples/typescript/src/roles/payment-processor/agent.ts rename to samples/typescript/src/roles/merchant-payment-processor-agent/agent.ts diff --git a/samples/typescript/src/roles/payment-processor/server.ts b/samples/typescript/src/roles/merchant-payment-processor-agent/server.ts similarity index 100% rename from samples/typescript/src/roles/payment-processor/server.ts rename to samples/typescript/src/roles/merchant-payment-processor-agent/server.ts diff --git a/samples/typescript/src/roles/payment-processor/tools.ts b/samples/typescript/src/roles/merchant-payment-processor-agent/tools.ts similarity index 100% rename from samples/typescript/src/roles/payment-processor/tools.ts rename to samples/typescript/src/roles/merchant-payment-processor-agent/tools.ts diff --git a/samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts b/samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts new file mode 100644 index 00000000..c87fccc4 --- /dev/null +++ b/samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts @@ -0,0 +1,74 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Merchant Payment Processor (PSP) MCP Server. + * + * Exposes one MCP tool consumed by the shopping agent v2 over stdio: + * initiate_payment + * + * NOTE: Minimum viable port. Mandate verification, token-store lookup, and + * receipt signing are STUBBED. Tool contract mirrors the v0.2 Python server. + */ + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { randomUUID } from 'node:crypto'; +import { z } from 'zod'; + +const server = new McpServer({ + name: 'merchant-payment-processor-mcp', + version: '0.2.0', +}); + +server.tool( + 'initiate_payment', + { + payment_token: z.string(), + checkout_jwt_hash: z.string(), + open_checkout_hash: z.string(), + }, + async ({ payment_token, checkout_jwt_hash, open_checkout_hash }) => { + if (!payment_token || !checkout_jwt_hash || !open_checkout_hash) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + error: 'missing_fields', + message: + 'payment_token, checkout_jwt_hash, and open_checkout_hash are required', + }), + }, + ], + }; + } + // STUB: real impl looks up token, verifies mandate chain, settles payment, + // signs receipt with PSP key, and POSTs to credentials-provider trigger. + const receipt = `psp_receipt.${randomUUID()}.stub_sig`; + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + status: 'settled', + payment_receipt: receipt, + checkout_jwt_hash, + open_checkout_hash, + amount: 1500, + currency: 'USD', + timestamp: Math.floor(Date.now() / 1000), + }), + }, + ], + }; + }, +); + +const transport = new StdioServerTransport(); +await server.connect(transport); diff --git a/samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts b/samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts new file mode 100644 index 00000000..94478653 --- /dev/null +++ b/samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * HTTP server for initiating payment processing. Runs on port 8083. + * Mirrors the Python `merchant_payment_processor_mcp/trigger_server.py`. + */ + +import express, { type Request, type Response } from 'express'; +import { randomUUID } from 'node:crypto'; + +const PORT = Number(process.env.MERCHANT_PAYMENT_PROCESSOR_TRIGGER_PORT ?? 8083); + +const app = express(); +app.use(express.json({ limit: '1mb' })); + +app.post('/initiate-payment', (req: Request, res: Response) => { + const { payment_token, checkout_jwt_hash, open_checkout_hash } = req.body ?? {}; + for (const [field, value] of [ + ['payment_token', payment_token], + ['checkout_jwt_hash', checkout_jwt_hash], + ['open_checkout_hash', open_checkout_hash], + ] as const) { + if (!value) return res.status(400).json({ error: `${field} required` }); + } + // STUB: real impl calls into MCP server's initiate_payment for token verification + settlement. + res.json({ + status: 'settled', + payment_receipt: `psp_receipt.${randomUUID()}.stub_sig`, + timestamp: Math.floor(Date.now() / 1000), + }); +}); + +app.listen(PORT, '127.0.0.1', () => { + console.log( + `Merchant payment processor trigger server: http://localhost:${PORT}/initiate-payment`, + ); +}); diff --git a/samples/typescript/src/roles/shopping-agent-v2/agent.ts b/samples/typescript/src/roles/shopping-agent-v2/agent.ts new file mode 100644 index 00000000..6ddccd83 --- /dev/null +++ b/samples/typescript/src/roles/shopping-agent-v2/agent.ts @@ -0,0 +1,194 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Shopping Agent v2 — Human-Not-Present. + * + * Single LlmAgent (MVP — Python has a consent/monitoring/purchase hierarchy) + * that owns three MCP toolsets (merchant, credentials-provider, payment-processor) + * plus the mandate helper tools. Each MCP toolset is a long-lived stdio Client + * connected to the corresponding `*-mcp/server.ts` subprocess. + */ + +import { FunctionTool, LlmAgent } from '@google/adk'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { z, type ZodTypeAny } from 'zod'; + +import { + assembleAndSignMandatesTool, + checkConstraintsAgainstMandateTool, + createCheckoutPresentationTool, + createPaymentPresentationTool, + verifyCheckoutReceiptTool, +} from './mandate-tools.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROLES_DIR = path.resolve(__dirname, '..'); + +async function makeMcpClient(serverEntry: string): Promise { + const transport = new StdioClientTransport({ + command: 'npx', + args: ['tsx', serverEntry], + env: { + ...process.env, + LOGS_DIR: process.env.LOGS_DIR ?? path.resolve(ROLES_DIR, '../../.logs'), + TEMP_DB_DIR: process.env.TEMP_DB_DIR ?? path.resolve(ROLES_DIR, '../../.temp-db'), + } as Record, + }); + const client = new Client({ name: 'shopping-agent-v2', version: '0.2.0' }); + await client.connect(transport); + return client; +} + +function mcpTool( + client: Client, + toolName: string, + description: string, + parameters: S, +): FunctionTool { + return new FunctionTool({ + name: toolName, + description, + parameters, + execute: async (args: unknown) => { + const result = (await client.callTool({ + name: toolName, + arguments: args as Record, + })) as { content?: Array<{ type: string; text?: string }> }; + const first = result.content?.[0]; + if (first?.type === 'text' && first.text) { + try { return JSON.parse(first.text); } catch { return first.text; } + } + return result; + }, + }); +} + +const merchantClient = await makeMcpClient(path.join(ROLES_DIR, 'merchant-agent-mcp/server.ts')); +const credentialsClient = await makeMcpClient(path.join(ROLES_DIR, 'credentials-provider-mcp/server.ts')); +const pspClient = await makeMcpClient(path.join(ROLES_DIR, 'merchant-payment-processor-mcp/server.ts')); + +const merchantTools = [ + mcpTool( + merchantClient, + 'search_inventory', + 'Search the merchant inventory by natural-language description. Returns at most one matching product. Stock is 0 until a price-drop trigger fires.', + z.object({ + product_description: z.string(), + constraint_price_cap: z.number().nullable().optional(), + }), + ), + mcpTool( + merchantClient, + 'check_product', + 'Return the current price and availability of a known item_id (e.g. supershoe_size_9_0). Stock becomes >0 only after the trigger fires.', + z.object({ + item_id: z.string(), + constraint_price_cap: z.number().nullable().optional(), + }), + ), + mcpTool( + merchantClient, + 'assemble_cart', + 'Build a cart for the given item_id and qty after check_product reports available=true.', + z.object({ item_id: z.string(), qty: z.number().int().positive() }), + ), + mcpTool( + merchantClient, + 'create_checkout', + 'Issue an ES256-signed checkout JWT for the cart and the open checkout mandate.', + z.object({ cart_id: z.string(), open_checkout_mandate_id: z.string() }), + ), + mcpTool( + merchantClient, + 'complete_checkout', + 'Hand the issued payment credential back to the merchant to finalize the order and emit a checkout receipt.', + z.object({ checkout_jwt: z.string(), payment_credential: z.unknown().optional() }), + ), +]; + +const credentialsTools = [ + mcpTool( + credentialsClient, + 'issue_payment_credential', + 'Verify the closed payment mandate chain and issue a scoped, single-use payment token.', + z.object({ + payment_mandate_chain_id: z.string(), + open_checkout_hash: z.string(), + checkout_jwt_hash: z.string(), + payment_nonce: z.string(), + }), + ), + mcpTool( + credentialsClient, + 'revoke_payment_credential', + 'Revoke a previously issued payment token.', + z.object({ payment_token: z.string() }), + ), + mcpTool( + credentialsClient, + 'verify_payment_receipt', + 'Verify a PSP-signed payment receipt against the issued payment token.', + z.object({ payment_receipt: z.string() }), + ), +]; + +const pspTools = [ + mcpTool( + pspClient, + 'initiate_payment', + 'Submit a settlement request to the PSP using the issued payment token. Returns a signed payment receipt.', + z.object({ + payment_token: z.string(), + checkout_jwt_hash: z.string(), + open_checkout_hash: z.string(), + }), + ), +]; + +export const shoppingAgentV2 = new LlmAgent({ + name: 'root_agent', + model: process.env.AGENT_MODEL ?? 'gemini-2.5-flash', + description: + 'Human-Not-Present shopping agent. Captures user intent, signs an open mandate, ' + + 'monitors merchant for price/availability, and autonomously executes the purchase ' + + 'when the constraint is satisfied.', + instruction: `You are a Human-Not-Present shopping agent. + +Flow: +1. Capture the user's intent (item, price cap, expiry). Call search_inventory once to register the item and get its item_id. +2. Call assembleAndSignMandates with the natural_language_description, constraint_price_cap, and an expires_at_iso string (1 hour from now). This produces open_checkout_mandate_id, open_payment_mandate_id, and their hashes. +3. To check current state, call check_product with the item_id from step 1. +4. Call checkConstraintsAgainstMandate with the open_checkout_mandate_id, current_price, and available. If satisfies = false, tell the user you'll keep watching and stop. +5. When satisfies = true, execute the autonomous purchase: + a. assemble_cart(item_id, qty=1) + b. create_checkout(cart_id, open_checkout_mandate_id) + c. createCheckoutPresentation, createPaymentPresentation + d. issue_payment_credential(payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash, payment_nonce — generate a random nonce) + e. initiate_payment(payment_token, checkout_jwt_hash, open_checkout_hash) + f. complete_checkout(checkout_jwt, payment_credential) + g. verify_payment_receipt, verifyCheckoutReceipt +6. Surface a brief receipt summary to the user. + +If a tool returns an object with an "error" key, STOP and return the error verbatim.`, + tools: [ + assembleAndSignMandatesTool, + checkConstraintsAgainstMandateTool, + createCheckoutPresentationTool, + createPaymentPresentationTool, + verifyCheckoutReceiptTool, + ...merchantTools, + ...credentialsTools, + ...pspTools, + ], +}); + +export { shoppingAgentV2 as rootAgent }; diff --git a/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts b/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts new file mode 100644 index 00000000..b69575c7 --- /dev/null +++ b/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts @@ -0,0 +1,110 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Mandate helper tools used by shopping-agent-v2. STUBS — see header in + * adjacent files. Names and arg shapes mirror Python `shopping_agent.mandate_tools`. + */ + +import { FunctionTool } from '@google/adk'; +import fs from 'node:fs'; +import path from 'node:path'; +import { randomUUID, createHash } from 'node:crypto'; +import { z } from 'zod'; + +const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; + +function sha256Base64Url(input: string): string { + return createHash('sha256').update(input).digest('base64url'); +} + +function persistMandate(filename: string, content: string): void { + fs.mkdirSync(TEMP_DB, { recursive: true }); + fs.writeFileSync(path.join(TEMP_DB, filename), content); +} + +export const assembleAndSignMandatesTool = new FunctionTool({ + name: 'assembleAndSignMandates', + description: + 'STUB: Builds and signs the open-checkout and open-payment mandate SD-JWT chains based on the user\'s intent and constraints. Persists them under TEMP_DB and returns their IDs and hashes.', + parameters: z.object({ + natural_language_description: z.string(), + constraint_price_cap: z.number(), + expires_at_iso: z.string(), + user_cart_confirmation_required: z.boolean().optional(), + }), + execute: async (args) => { + const openCheckoutId = `open_chk_${randomUUID()}`; + const openPaymentId = `open_pay_${randomUUID()}`; + const checkoutBody = JSON.stringify({ ...args, kind: 'open_checkout', id: openCheckoutId }); + const paymentBody = JSON.stringify({ ...args, kind: 'open_payment', id: openPaymentId }); + persistMandate(`${openCheckoutId}.sdjwt`, `stub.${Buffer.from(checkoutBody).toString('base64url')}.sig`); + persistMandate(`${openPaymentId}.sdjwt`, `stub.${Buffer.from(paymentBody).toString('base64url')}.sig`); + return { + open_checkout_mandate_id: openCheckoutId, + open_checkout_hash: sha256Base64Url(checkoutBody), + open_payment_mandate_id: openPaymentId, + open_payment_hash: sha256Base64Url(paymentBody), + }; + }, +}); + +export const checkConstraintsAgainstMandateTool = new FunctionTool({ + name: 'checkConstraintsAgainstMandate', + description: + 'Check whether the current price + availability of an item satisfies the constraints in the open mandate.', + parameters: z.object({ + open_checkout_mandate_id: z.string(), + current_price: z.number(), + available: z.boolean(), + }), + execute: async ({ available, current_price }) => { + if (!available) return { satisfies: false, reason: 'item_not_available' }; + if (current_price <= 0) return { satisfies: false, reason: 'invalid_price' }; + return { satisfies: true }; + }, +}); + +export const createCheckoutPresentationTool = new FunctionTool({ + name: 'createCheckoutPresentation', + description: 'STUB: Build the closed-checkout mandate SD-JWT presentation from the open mandate and a concrete cart.', + parameters: z.object({ + open_checkout_mandate_id: z.string(), + checkout_jwt_hash: z.string(), + cart_id: z.string(), + }), + execute: async (args) => { + const id = `chk_${randomUUID()}`; + persistMandate(`${id}.sdjwt`, `stub.${Buffer.from(JSON.stringify(args)).toString('base64url')}.sig`); + return { checkout_mandate_chain_id: id }; + }, +}); + +export const createPaymentPresentationTool = new FunctionTool({ + name: 'createPaymentPresentation', + description: 'STUB: Build the closed-payment mandate SD-JWT presentation.', + parameters: z.object({ + open_payment_mandate_id: z.string(), + checkout_jwt_hash: z.string(), + payment_nonce: z.string(), + }), + execute: async (args) => { + const id = `pay_${randomUUID()}`; + persistMandate(`${id}.sdjwt`, `stub.${Buffer.from(JSON.stringify(args)).toString('base64url')}.sig`); + return { payment_mandate_chain_id: id }; + }, +}); + +export const verifyCheckoutReceiptTool = new FunctionTool({ + name: 'verifyCheckoutReceipt', + description: 'STUB: Verify the signed checkout receipt returned by complete_checkout.', + parameters: z.object({ + receipt_signature: z.string().optional(), + }), + execute: async ({ receipt_signature }) => ({ verified: Boolean(receipt_signature) }), +}); diff --git a/samples/typescript/src/roles/shopping-agent-v2/server.ts b/samples/typescript/src/roles/shopping-agent-v2/server.ts new file mode 100644 index 00000000..03613d08 --- /dev/null +++ b/samples/typescript/src/roles/shopping-agent-v2/server.ts @@ -0,0 +1,83 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Shopping Agent v2 A2A server (Human-Not-Present). + * Mirrors the Python `shopping_agent_v2/run_server.py`. + */ + +import { Runner } from '@google/adk'; +import type { AgentCard } from '@a2a-js/sdk'; + +import { shoppingAgentV2 } from './agent.js'; +import { sessionService } from '../../common/config/session.js'; +import { BaseAgentExecutor } from '../../common/server/base-executor.js'; +import { bootstrapServer } from '../../common/server/bootstrap.js'; + +const PORT = Number(process.env.AGENT_PORT ?? 8080); + +const runner = new Runner({ + appName: 'ap2-shopping-v2', + agent: shoppingAgentV2, + sessionService, +}); + +const agentExecutor = new BaseAgentExecutor({ + agentName: 'shopping_agent', + appName: 'ap2-shopping-v2', + runner, + maxLlmCalls: 20, + workingMessage: 'The shopping agent is monitoring your drop...', +}); + +const agentCard: AgentCard = { + name: 'Shopping Agent v2', + description: + 'Human-Not-Present shopping agent. Holds signed open mandates and ' + + 'autonomously executes purchases when price/availability constraints are met.', + url: `http://localhost:${PORT}/a2a/shopping_agent`, + provider: { organization: 'AP2 TypeScript samples', url: 'https://github.com/google-agentic-commerce/AP2' }, + skills: [ + { + id: 'monitor_and_purchase', + name: 'Monitor and Purchase', + description: + 'Captures user intent, signs open mandates, monitors the merchant for ' + + 'price/availability changes, and executes purchase autonomously.', + parameters: { + type: 'object', + properties: { + user_intent: { + type: 'string', + description: 'Natural-language description of what to buy and the constraints.', + }, + }, + required: ['user_intent'], + }, + tags: ['shopping', 'hnp', 'autonomous'], + } as AgentCard['skills'][number], + ], + capabilities: { + streaming: true, + pushNotifications: false, + stateTransitionHistory: true, + extensions: [ + { + uri: 'https://github.com/google-agentic-commerce/ap2/v1', + description: 'Supports the Agent Payments Protocol.', + required: true, + }, + ], + }, + defaultInputModes: ['application/json'], + defaultOutputModes: ['application/json'], + protocolVersion: '0.3.0', + version: '0.2.0', +}; + +bootstrapServer({ agentCard, agentExecutor, port: PORT, label: 'Shopping Agent v2' }); diff --git a/samples/typescript/src/roles/shopping/agent.ts b/samples/typescript/src/roles/shopping-agent/agent.ts similarity index 100% rename from samples/typescript/src/roles/shopping/agent.ts rename to samples/typescript/src/roles/shopping-agent/agent.ts diff --git a/samples/typescript/src/roles/shopping/server.ts b/samples/typescript/src/roles/shopping-agent/server.ts similarity index 100% rename from samples/typescript/src/roles/shopping/server.ts rename to samples/typescript/src/roles/shopping-agent/server.ts diff --git a/samples/typescript/src/roles/shopping/subagents/payment-collector/agent.ts b/samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/agent.ts similarity index 100% rename from samples/typescript/src/roles/shopping/subagents/payment-collector/agent.ts rename to samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/agent.ts diff --git a/samples/typescript/src/roles/shopping/subagents/payment-collector/tools.ts b/samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/tools.ts similarity index 100% rename from samples/typescript/src/roles/shopping/subagents/payment-collector/tools.ts rename to samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/tools.ts diff --git a/samples/typescript/src/roles/shopping/subagents/shipping-collector/agent.ts b/samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/agent.ts similarity index 100% rename from samples/typescript/src/roles/shopping/subagents/shipping-collector/agent.ts rename to samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/agent.ts diff --git a/samples/typescript/src/roles/shopping/subagents/shipping-collector/tools.ts b/samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/tools.ts similarity index 100% rename from samples/typescript/src/roles/shopping/subagents/shipping-collector/tools.ts rename to samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/tools.ts diff --git a/samples/typescript/src/roles/shopping/subagents/shopper/agent.ts b/samples/typescript/src/roles/shopping-agent/subagents/shopper/agent.ts similarity index 100% rename from samples/typescript/src/roles/shopping/subagents/shopper/agent.ts rename to samples/typescript/src/roles/shopping-agent/subagents/shopper/agent.ts diff --git a/samples/typescript/src/roles/shopping/subagents/shopper/tools.ts b/samples/typescript/src/roles/shopping-agent/subagents/shopper/tools.ts similarity index 100% rename from samples/typescript/src/roles/shopping/subagents/shopper/tools.ts rename to samples/typescript/src/roles/shopping-agent/subagents/shopper/tools.ts diff --git a/samples/typescript/src/roles/shopping/tools.ts b/samples/typescript/src/roles/shopping-agent/tools.ts similarity index 100% rename from samples/typescript/src/roles/shopping/tools.ts rename to samples/typescript/src/roles/shopping-agent/tools.ts From 42a1872a4582aef159157550a0b2d8eb003d9a94 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 30 May 2026 14:18:14 -0500 Subject: [PATCH 03/17] refactor(typescript): move samples to code/samples/typescript (v0.2 layout) Aligns the TypeScript samples with the v0.2 repository structure where all language samples live under code/samples/. Also fixes two pre-existing typecheck errors surfaced by the move: - shopping-agent/agent.ts imported the old subagent folder names (shipping-collector/payment-collector) instead of the renamed shipping-address-collector/payment-method-collector. - shopping-agent-v2 mcpTool helper used a ZodTypeAny generic that ADK's FunctionTool rejects; narrowed to z.ZodObject. Path/CI updates: - HNP run.sh REPO_ROOT depth (../../ -> ../../../) - linter.yaml working-directory + FILTER_REGEX_EXCLUDE (keep linting TS, still skip python/go/android/certs under code/samples/) - .eslintrc.json project + files globs - README path references tsc --noEmit and npm run lint both pass. --- .github/linters/.eslintrc.json | 6 +++--- .github/workflows/linter.yaml | 4 ++-- {samples => code/samples}/typescript/.env.example | 0 {samples => code/samples}/typescript/.gitignore | 0 {samples => code/samples}/typescript/README.md | 2 +- {samples => code/samples}/typescript/eslint.config.js | 0 {samples => code/samples}/typescript/package-lock.json | 0 {samples => code/samples}/typescript/package.json | 0 .../scenarios/a2a/human-not-present/cards/README.md | 4 ++-- .../scenarios/a2a/human-not-present/cards/run.sh | 2 +- .../scenarios/a2a/human-present/cards/README.md | 10 +++++----- .../scenarios/a2a/human-present/cards/run.sh | 0 .../samples}/typescript/src/common/config/session.ts | 0 .../samples}/typescript/src/common/constants/index.ts | 0 .../typescript/src/common/schemas/cart-mandate.ts | 0 .../typescript/src/common/schemas/intent-mandate.ts | 0 .../typescript/src/common/schemas/payment-mandate.ts | 0 .../typescript/src/common/schemas/payment-receipt.ts | 0 .../typescript/src/common/schemas/shipping-address.ts | 0 .../typescript/src/common/server/a2a-context.ts | 0 .../typescript/src/common/server/base-executor.ts | 0 .../samples}/typescript/src/common/server/bootstrap.ts | 0 .../typescript/src/common/server/middleware.ts | 0 .../typescript/src/common/types/cart-mandate.ts | 0 .../typescript/src/common/types/intent-mandate.ts | 0 .../typescript/src/common/types/payment-item.ts | 0 .../typescript/src/common/types/payment-mandate.ts | 0 .../typescript/src/common/types/payment-receipt.ts | 0 .../samples}/typescript/src/common/utils/artifact.ts | 0 .../samples}/typescript/src/common/utils/message.ts | 0 .../samples}/typescript/src/common/vc/ap2-context.ts | 0 .../samples}/typescript/src/common/vc/credentials.ts | 0 .../typescript/src/common/vc/document-loader.ts | 0 .../samples}/typescript/src/common/vc/index.ts | 0 .../samples}/typescript/src/common/vc/key-manager.ts | 0 .../samples}/typescript/src/common/vc/types.ts | 0 .../credentials-provider-agent/account-manager.ts | 0 .../src/roles/credentials-provider-agent/agent.ts | 0 .../src/roles/credentials-provider-agent/server.ts | 0 .../src/roles/credentials-provider-agent/tools.ts | 0 .../src/roles/credentials-provider-mcp/server.ts | 0 .../roles/credentials-provider-mcp/trigger-server.ts | 0 .../samples}/typescript/src/roles/index.ts | 0 .../typescript/src/roles/merchant-agent-mcp/server.ts | 0 .../src/roles/merchant-agent-mcp/trigger-server.ts | 0 .../typescript/src/roles/merchant-agent/agent.ts | 0 .../typescript/src/roles/merchant-agent/server.ts | 0 .../typescript/src/roles/merchant-agent/storage.ts | 0 .../typescript/src/roles/merchant-agent/tools.ts | 0 .../roles/merchant-payment-processor-agent/agent.ts | 0 .../roles/merchant-payment-processor-agent/server.ts | 0 .../roles/merchant-payment-processor-agent/tools.ts | 0 .../src/roles/merchant-payment-processor-mcp/server.ts | 0 .../merchant-payment-processor-mcp/trigger-server.ts | 0 .../typescript/src/roles/shopping-agent-v2/agent.ts | 6 +++--- .../src/roles/shopping-agent-v2/mandate-tools.ts | 0 .../typescript/src/roles/shopping-agent-v2/server.ts | 0 .../typescript/src/roles/shopping-agent/agent.ts | 4 ++-- .../typescript/src/roles/shopping-agent/server.ts | 0 .../subagents/payment-method-collector/agent.ts | 0 .../subagents/payment-method-collector/tools.ts | 0 .../subagents/shipping-address-collector/agent.ts | 0 .../subagents/shipping-address-collector/tools.ts | 0 .../roles/shopping-agent/subagents/shopper/agent.ts | 0 .../roles/shopping-agent/subagents/shopper/tools.ts | 0 .../typescript/src/roles/shopping-agent/tools.ts | 0 .../samples}/typescript/test/e2e/connectivity.test.ts | 0 .../samples}/typescript/test/e2e/payment-flow.test.ts | 0 {samples => code/samples}/typescript/tsconfig.json | 0 {samples => code/samples}/typescript/vitest.config.ts | 0 70 files changed, 19 insertions(+), 19 deletions(-) rename {samples => code/samples}/typescript/.env.example (100%) rename {samples => code/samples}/typescript/.gitignore (100%) rename {samples => code/samples}/typescript/README.md (99%) rename {samples => code/samples}/typescript/eslint.config.js (100%) rename {samples => code/samples}/typescript/package-lock.json (100%) rename {samples => code/samples}/typescript/package.json (100%) rename {samples => code/samples}/typescript/scenarios/a2a/human-not-present/cards/README.md (96%) rename {samples => code/samples}/typescript/scenarios/a2a/human-not-present/cards/run.sh (98%) rename {samples => code/samples}/typescript/scenarios/a2a/human-present/cards/README.md (93%) rename {samples => code/samples}/typescript/scenarios/a2a/human-present/cards/run.sh (100%) rename {samples => code/samples}/typescript/src/common/config/session.ts (100%) rename {samples => code/samples}/typescript/src/common/constants/index.ts (100%) rename {samples => code/samples}/typescript/src/common/schemas/cart-mandate.ts (100%) rename {samples => code/samples}/typescript/src/common/schemas/intent-mandate.ts (100%) rename {samples => code/samples}/typescript/src/common/schemas/payment-mandate.ts (100%) rename {samples => code/samples}/typescript/src/common/schemas/payment-receipt.ts (100%) rename {samples => code/samples}/typescript/src/common/schemas/shipping-address.ts (100%) rename {samples => code/samples}/typescript/src/common/server/a2a-context.ts (100%) rename {samples => code/samples}/typescript/src/common/server/base-executor.ts (100%) rename {samples => code/samples}/typescript/src/common/server/bootstrap.ts (100%) rename {samples => code/samples}/typescript/src/common/server/middleware.ts (100%) rename {samples => code/samples}/typescript/src/common/types/cart-mandate.ts (100%) rename {samples => code/samples}/typescript/src/common/types/intent-mandate.ts (100%) rename {samples => code/samples}/typescript/src/common/types/payment-item.ts (100%) rename {samples => code/samples}/typescript/src/common/types/payment-mandate.ts (100%) rename {samples => code/samples}/typescript/src/common/types/payment-receipt.ts (100%) rename {samples => code/samples}/typescript/src/common/utils/artifact.ts (100%) rename {samples => code/samples}/typescript/src/common/utils/message.ts (100%) rename {samples => code/samples}/typescript/src/common/vc/ap2-context.ts (100%) rename {samples => code/samples}/typescript/src/common/vc/credentials.ts (100%) rename {samples => code/samples}/typescript/src/common/vc/document-loader.ts (100%) rename {samples => code/samples}/typescript/src/common/vc/index.ts (100%) rename {samples => code/samples}/typescript/src/common/vc/key-manager.ts (100%) rename {samples => code/samples}/typescript/src/common/vc/types.ts (100%) rename {samples => code/samples}/typescript/src/roles/credentials-provider-agent/account-manager.ts (100%) rename {samples => code/samples}/typescript/src/roles/credentials-provider-agent/agent.ts (100%) rename {samples => code/samples}/typescript/src/roles/credentials-provider-agent/server.ts (100%) rename {samples => code/samples}/typescript/src/roles/credentials-provider-agent/tools.ts (100%) rename {samples => code/samples}/typescript/src/roles/credentials-provider-mcp/server.ts (100%) rename {samples => code/samples}/typescript/src/roles/credentials-provider-mcp/trigger-server.ts (100%) rename {samples => code/samples}/typescript/src/roles/index.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-agent-mcp/server.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-agent-mcp/trigger-server.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-agent/agent.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-agent/server.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-agent/storage.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-agent/tools.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-payment-processor-agent/agent.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-payment-processor-agent/server.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-payment-processor-agent/tools.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-payment-processor-mcp/server.ts (100%) rename {samples => code/samples}/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts (100%) rename {samples => code/samples}/typescript/src/roles/shopping-agent-v2/agent.ts (98%) rename {samples => code/samples}/typescript/src/roles/shopping-agent-v2/mandate-tools.ts (100%) rename {samples => code/samples}/typescript/src/roles/shopping-agent-v2/server.ts (100%) rename {samples => code/samples}/typescript/src/roles/shopping-agent/agent.ts (97%) rename {samples => code/samples}/typescript/src/roles/shopping-agent/server.ts (100%) rename {samples => code/samples}/typescript/src/roles/shopping-agent/subagents/payment-method-collector/agent.ts (100%) rename {samples => code/samples}/typescript/src/roles/shopping-agent/subagents/payment-method-collector/tools.ts (100%) rename {samples => code/samples}/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/agent.ts (100%) rename {samples => code/samples}/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/tools.ts (100%) rename {samples => code/samples}/typescript/src/roles/shopping-agent/subagents/shopper/agent.ts (100%) rename {samples => code/samples}/typescript/src/roles/shopping-agent/subagents/shopper/tools.ts (100%) rename {samples => code/samples}/typescript/src/roles/shopping-agent/tools.ts (100%) rename {samples => code/samples}/typescript/test/e2e/connectivity.test.ts (100%) rename {samples => code/samples}/typescript/test/e2e/payment-flow.test.ts (100%) rename {samples => code/samples}/typescript/tsconfig.json (100%) rename {samples => code/samples}/typescript/vitest.config.ts (100%) diff --git a/.github/linters/.eslintrc.json b/.github/linters/.eslintrc.json index 07583b4a..c60a1857 100644 --- a/.github/linters/.eslintrc.json +++ b/.github/linters/.eslintrc.json @@ -19,15 +19,15 @@ }, "overrides": [ { - "files": ["samples/**/*.ts"], + "files": ["code/samples/**/*.ts"], "parserOptions": { - "project": "./samples/typescript/tsconfig.json" + "project": "./code/samples/typescript/tsconfig.json" }, "settings": { "import/resolver": { "typescript": { "alwaysTryTypes": true, - "project": "./samples/typescript/tsconfig.json" + "project": "./code/samples/typescript/tsconfig.json" } } }, diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml index 85164dbe..3987af44 100644 --- a/.github/workflows/linter.yaml +++ b/.github/workflows/linter.yaml @@ -21,7 +21,7 @@ jobs: node-version: "20" - name: Install TypeScript Sample Dependencies - working-directory: samples/typescript + working-directory: code/samples/typescript run: npm ci - name: Lint Code Base @@ -32,7 +32,7 @@ jobs: LOG_LEVEL: WARN SHELLCHECK_OPTS: -e SC1091 -e 2086 VALIDATE_ALL_CODEBASE: false - FILTER_REGEX_EXCLUDE: "^(\\.github/|\\.vscode/|code/samples/).*|CODE_OF_CONDUCT.md|CHANGELOG.md" + FILTER_REGEX_EXCLUDE: "^(\\.github/|\\.vscode/|code/samples/(python|go|android|certs)/).*|CODE_OF_CONDUCT.md|CHANGELOG.md" VALIDATE_BIOME_FORMAT: false VALIDATE_PYTHON_BLACK: false VALIDATE_PYTHON_FLAKE8: false diff --git a/samples/typescript/.env.example b/code/samples/typescript/.env.example similarity index 100% rename from samples/typescript/.env.example rename to code/samples/typescript/.env.example diff --git a/samples/typescript/.gitignore b/code/samples/typescript/.gitignore similarity index 100% rename from samples/typescript/.gitignore rename to code/samples/typescript/.gitignore diff --git a/samples/typescript/README.md b/code/samples/typescript/README.md similarity index 99% rename from samples/typescript/README.md rename to code/samples/typescript/README.md index a34f10bd..97af14e1 100644 --- a/samples/typescript/README.md +++ b/code/samples/typescript/README.md @@ -27,7 +27,7 @@ Node.js, or edge runtimes: ## Project Structure ```text -samples/typescript/ +code/samples/typescript/ ├── src/ │ ├── roles/ # Agent role implementations and entry points │ │ ├── shopping/ # Shopping Agent (root orchestrator) diff --git a/samples/typescript/eslint.config.js b/code/samples/typescript/eslint.config.js similarity index 100% rename from samples/typescript/eslint.config.js rename to code/samples/typescript/eslint.config.js diff --git a/samples/typescript/package-lock.json b/code/samples/typescript/package-lock.json similarity index 100% rename from samples/typescript/package-lock.json rename to code/samples/typescript/package-lock.json diff --git a/samples/typescript/package.json b/code/samples/typescript/package.json similarity index 100% rename from samples/typescript/package.json rename to code/samples/typescript/package.json diff --git a/samples/typescript/scenarios/a2a/human-not-present/cards/README.md b/code/samples/typescript/scenarios/a2a/human-not-present/cards/README.md similarity index 96% rename from samples/typescript/scenarios/a2a/human-not-present/cards/README.md rename to code/samples/typescript/scenarios/a2a/human-not-present/cards/README.md index 97677101..c0d0d9fd 100644 --- a/samples/typescript/scenarios/a2a/human-not-present/cards/README.md +++ b/code/samples/typescript/scenarios/a2a/human-not-present/cards/README.md @@ -52,7 +52,7 @@ echo "GOOGLE_API_KEY=your_key" > .env ## Execution ```sh -bash samples/typescript/scenarios/a2a/human-not-present/cards/run.sh +bash code/samples/typescript/scenarios/a2a/human-not-present/cards/run.sh ``` Ports: @@ -69,7 +69,7 @@ Ports: 1. Start the stack: ```sh - bash samples/typescript/scenarios/a2a/human-not-present/cards/run.sh + bash code/samples/typescript/scenarios/a2a/human-not-present/cards/run.sh ``` 2. Send the initial intent to the shopping agent over A2A (the agent runs diff --git a/samples/typescript/scenarios/a2a/human-not-present/cards/run.sh b/code/samples/typescript/scenarios/a2a/human-not-present/cards/run.sh similarity index 98% rename from samples/typescript/scenarios/a2a/human-not-present/cards/run.sh rename to code/samples/typescript/scenarios/a2a/human-not-present/cards/run.sh index 5c546e7e..6f6664f0 100755 --- a/samples/typescript/scenarios/a2a/human-not-present/cards/run.sh +++ b/code/samples/typescript/scenarios/a2a/human-not-present/cards/run.sh @@ -11,7 +11,7 @@ set -eu readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly TS_SAMPLES_ROOT="$(cd "$SCRIPT_DIR/../../../../" && pwd)" -readonly REPO_ROOT="$(cd "$TS_SAMPLES_ROOT/../../" && pwd)" +readonly REPO_ROOT="$(cd "$TS_SAMPLES_ROOT/../../../" && pwd)" readonly LOG_DIR="$SCRIPT_DIR/.logs" readonly TEMP_DB_DIR="$SCRIPT_DIR/.temp-db" diff --git a/samples/typescript/scenarios/a2a/human-present/cards/README.md b/code/samples/typescript/scenarios/a2a/human-present/cards/README.md similarity index 93% rename from samples/typescript/scenarios/a2a/human-present/cards/README.md rename to code/samples/typescript/scenarios/a2a/human-present/cards/README.md index af68d697..dcbc327c 100644 --- a/samples/typescript/scenarios/a2a/human-present/cards/README.md +++ b/code/samples/typescript/scenarios/a2a/human-present/cards/README.md @@ -53,11 +53,11 @@ All four roles are implemented in TypeScript: Obtain a Google API key from [Google AI Studio](https://aistudio.google.com/apikey), then create a `.env` -file in `samples/typescript/`: +file in `code/samples/typescript/`: ```sh -cp samples/typescript/.env.example samples/typescript/.env -# Edit samples/typescript/.env and fill in GOOGLE_API_KEY +cp code/samples/typescript/.env.example code/samples/typescript/.env +# Edit code/samples/typescript/.env and fill in GOOGLE_API_KEY ``` Alternatively, configure Vertex AI by setting `GOOGLE_GENAI_USE_VERTEXAI=true` @@ -66,7 +66,7 @@ along with `GOOGLE_CLOUD_PROJECT` and `GOOGLE_CLOUD_LOCATION`. ### Install dependencies ```sh -cd samples/typescript +cd code/samples/typescript npm install ``` @@ -75,7 +75,7 @@ npm install ### Option 1: Run everything with one command ```sh -bash samples/typescript/scenarios/a2a/human-present/cards/run.sh +bash code/samples/typescript/scenarios/a2a/human-present/cards/run.sh ``` This starts the three backend agents and the Shopping Agent web UI. diff --git a/samples/typescript/scenarios/a2a/human-present/cards/run.sh b/code/samples/typescript/scenarios/a2a/human-present/cards/run.sh similarity index 100% rename from samples/typescript/scenarios/a2a/human-present/cards/run.sh rename to code/samples/typescript/scenarios/a2a/human-present/cards/run.sh diff --git a/samples/typescript/src/common/config/session.ts b/code/samples/typescript/src/common/config/session.ts similarity index 100% rename from samples/typescript/src/common/config/session.ts rename to code/samples/typescript/src/common/config/session.ts diff --git a/samples/typescript/src/common/constants/index.ts b/code/samples/typescript/src/common/constants/index.ts similarity index 100% rename from samples/typescript/src/common/constants/index.ts rename to code/samples/typescript/src/common/constants/index.ts diff --git a/samples/typescript/src/common/schemas/cart-mandate.ts b/code/samples/typescript/src/common/schemas/cart-mandate.ts similarity index 100% rename from samples/typescript/src/common/schemas/cart-mandate.ts rename to code/samples/typescript/src/common/schemas/cart-mandate.ts diff --git a/samples/typescript/src/common/schemas/intent-mandate.ts b/code/samples/typescript/src/common/schemas/intent-mandate.ts similarity index 100% rename from samples/typescript/src/common/schemas/intent-mandate.ts rename to code/samples/typescript/src/common/schemas/intent-mandate.ts diff --git a/samples/typescript/src/common/schemas/payment-mandate.ts b/code/samples/typescript/src/common/schemas/payment-mandate.ts similarity index 100% rename from samples/typescript/src/common/schemas/payment-mandate.ts rename to code/samples/typescript/src/common/schemas/payment-mandate.ts diff --git a/samples/typescript/src/common/schemas/payment-receipt.ts b/code/samples/typescript/src/common/schemas/payment-receipt.ts similarity index 100% rename from samples/typescript/src/common/schemas/payment-receipt.ts rename to code/samples/typescript/src/common/schemas/payment-receipt.ts diff --git a/samples/typescript/src/common/schemas/shipping-address.ts b/code/samples/typescript/src/common/schemas/shipping-address.ts similarity index 100% rename from samples/typescript/src/common/schemas/shipping-address.ts rename to code/samples/typescript/src/common/schemas/shipping-address.ts diff --git a/samples/typescript/src/common/server/a2a-context.ts b/code/samples/typescript/src/common/server/a2a-context.ts similarity index 100% rename from samples/typescript/src/common/server/a2a-context.ts rename to code/samples/typescript/src/common/server/a2a-context.ts diff --git a/samples/typescript/src/common/server/base-executor.ts b/code/samples/typescript/src/common/server/base-executor.ts similarity index 100% rename from samples/typescript/src/common/server/base-executor.ts rename to code/samples/typescript/src/common/server/base-executor.ts diff --git a/samples/typescript/src/common/server/bootstrap.ts b/code/samples/typescript/src/common/server/bootstrap.ts similarity index 100% rename from samples/typescript/src/common/server/bootstrap.ts rename to code/samples/typescript/src/common/server/bootstrap.ts diff --git a/samples/typescript/src/common/server/middleware.ts b/code/samples/typescript/src/common/server/middleware.ts similarity index 100% rename from samples/typescript/src/common/server/middleware.ts rename to code/samples/typescript/src/common/server/middleware.ts diff --git a/samples/typescript/src/common/types/cart-mandate.ts b/code/samples/typescript/src/common/types/cart-mandate.ts similarity index 100% rename from samples/typescript/src/common/types/cart-mandate.ts rename to code/samples/typescript/src/common/types/cart-mandate.ts diff --git a/samples/typescript/src/common/types/intent-mandate.ts b/code/samples/typescript/src/common/types/intent-mandate.ts similarity index 100% rename from samples/typescript/src/common/types/intent-mandate.ts rename to code/samples/typescript/src/common/types/intent-mandate.ts diff --git a/samples/typescript/src/common/types/payment-item.ts b/code/samples/typescript/src/common/types/payment-item.ts similarity index 100% rename from samples/typescript/src/common/types/payment-item.ts rename to code/samples/typescript/src/common/types/payment-item.ts diff --git a/samples/typescript/src/common/types/payment-mandate.ts b/code/samples/typescript/src/common/types/payment-mandate.ts similarity index 100% rename from samples/typescript/src/common/types/payment-mandate.ts rename to code/samples/typescript/src/common/types/payment-mandate.ts diff --git a/samples/typescript/src/common/types/payment-receipt.ts b/code/samples/typescript/src/common/types/payment-receipt.ts similarity index 100% rename from samples/typescript/src/common/types/payment-receipt.ts rename to code/samples/typescript/src/common/types/payment-receipt.ts diff --git a/samples/typescript/src/common/utils/artifact.ts b/code/samples/typescript/src/common/utils/artifact.ts similarity index 100% rename from samples/typescript/src/common/utils/artifact.ts rename to code/samples/typescript/src/common/utils/artifact.ts diff --git a/samples/typescript/src/common/utils/message.ts b/code/samples/typescript/src/common/utils/message.ts similarity index 100% rename from samples/typescript/src/common/utils/message.ts rename to code/samples/typescript/src/common/utils/message.ts diff --git a/samples/typescript/src/common/vc/ap2-context.ts b/code/samples/typescript/src/common/vc/ap2-context.ts similarity index 100% rename from samples/typescript/src/common/vc/ap2-context.ts rename to code/samples/typescript/src/common/vc/ap2-context.ts diff --git a/samples/typescript/src/common/vc/credentials.ts b/code/samples/typescript/src/common/vc/credentials.ts similarity index 100% rename from samples/typescript/src/common/vc/credentials.ts rename to code/samples/typescript/src/common/vc/credentials.ts diff --git a/samples/typescript/src/common/vc/document-loader.ts b/code/samples/typescript/src/common/vc/document-loader.ts similarity index 100% rename from samples/typescript/src/common/vc/document-loader.ts rename to code/samples/typescript/src/common/vc/document-loader.ts diff --git a/samples/typescript/src/common/vc/index.ts b/code/samples/typescript/src/common/vc/index.ts similarity index 100% rename from samples/typescript/src/common/vc/index.ts rename to code/samples/typescript/src/common/vc/index.ts diff --git a/samples/typescript/src/common/vc/key-manager.ts b/code/samples/typescript/src/common/vc/key-manager.ts similarity index 100% rename from samples/typescript/src/common/vc/key-manager.ts rename to code/samples/typescript/src/common/vc/key-manager.ts diff --git a/samples/typescript/src/common/vc/types.ts b/code/samples/typescript/src/common/vc/types.ts similarity index 100% rename from samples/typescript/src/common/vc/types.ts rename to code/samples/typescript/src/common/vc/types.ts diff --git a/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts b/code/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts similarity index 100% rename from samples/typescript/src/roles/credentials-provider-agent/account-manager.ts rename to code/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts diff --git a/samples/typescript/src/roles/credentials-provider-agent/agent.ts b/code/samples/typescript/src/roles/credentials-provider-agent/agent.ts similarity index 100% rename from samples/typescript/src/roles/credentials-provider-agent/agent.ts rename to code/samples/typescript/src/roles/credentials-provider-agent/agent.ts diff --git a/samples/typescript/src/roles/credentials-provider-agent/server.ts b/code/samples/typescript/src/roles/credentials-provider-agent/server.ts similarity index 100% rename from samples/typescript/src/roles/credentials-provider-agent/server.ts rename to code/samples/typescript/src/roles/credentials-provider-agent/server.ts diff --git a/samples/typescript/src/roles/credentials-provider-agent/tools.ts b/code/samples/typescript/src/roles/credentials-provider-agent/tools.ts similarity index 100% rename from samples/typescript/src/roles/credentials-provider-agent/tools.ts rename to code/samples/typescript/src/roles/credentials-provider-agent/tools.ts diff --git a/samples/typescript/src/roles/credentials-provider-mcp/server.ts b/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts similarity index 100% rename from samples/typescript/src/roles/credentials-provider-mcp/server.ts rename to code/samples/typescript/src/roles/credentials-provider-mcp/server.ts diff --git a/samples/typescript/src/roles/credentials-provider-mcp/trigger-server.ts b/code/samples/typescript/src/roles/credentials-provider-mcp/trigger-server.ts similarity index 100% rename from samples/typescript/src/roles/credentials-provider-mcp/trigger-server.ts rename to code/samples/typescript/src/roles/credentials-provider-mcp/trigger-server.ts diff --git a/samples/typescript/src/roles/index.ts b/code/samples/typescript/src/roles/index.ts similarity index 100% rename from samples/typescript/src/roles/index.ts rename to code/samples/typescript/src/roles/index.ts diff --git a/samples/typescript/src/roles/merchant-agent-mcp/server.ts b/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts similarity index 100% rename from samples/typescript/src/roles/merchant-agent-mcp/server.ts rename to code/samples/typescript/src/roles/merchant-agent-mcp/server.ts diff --git a/samples/typescript/src/roles/merchant-agent-mcp/trigger-server.ts b/code/samples/typescript/src/roles/merchant-agent-mcp/trigger-server.ts similarity index 100% rename from samples/typescript/src/roles/merchant-agent-mcp/trigger-server.ts rename to code/samples/typescript/src/roles/merchant-agent-mcp/trigger-server.ts diff --git a/samples/typescript/src/roles/merchant-agent/agent.ts b/code/samples/typescript/src/roles/merchant-agent/agent.ts similarity index 100% rename from samples/typescript/src/roles/merchant-agent/agent.ts rename to code/samples/typescript/src/roles/merchant-agent/agent.ts diff --git a/samples/typescript/src/roles/merchant-agent/server.ts b/code/samples/typescript/src/roles/merchant-agent/server.ts similarity index 100% rename from samples/typescript/src/roles/merchant-agent/server.ts rename to code/samples/typescript/src/roles/merchant-agent/server.ts diff --git a/samples/typescript/src/roles/merchant-agent/storage.ts b/code/samples/typescript/src/roles/merchant-agent/storage.ts similarity index 100% rename from samples/typescript/src/roles/merchant-agent/storage.ts rename to code/samples/typescript/src/roles/merchant-agent/storage.ts diff --git a/samples/typescript/src/roles/merchant-agent/tools.ts b/code/samples/typescript/src/roles/merchant-agent/tools.ts similarity index 100% rename from samples/typescript/src/roles/merchant-agent/tools.ts rename to code/samples/typescript/src/roles/merchant-agent/tools.ts diff --git a/samples/typescript/src/roles/merchant-payment-processor-agent/agent.ts b/code/samples/typescript/src/roles/merchant-payment-processor-agent/agent.ts similarity index 100% rename from samples/typescript/src/roles/merchant-payment-processor-agent/agent.ts rename to code/samples/typescript/src/roles/merchant-payment-processor-agent/agent.ts diff --git a/samples/typescript/src/roles/merchant-payment-processor-agent/server.ts b/code/samples/typescript/src/roles/merchant-payment-processor-agent/server.ts similarity index 100% rename from samples/typescript/src/roles/merchant-payment-processor-agent/server.ts rename to code/samples/typescript/src/roles/merchant-payment-processor-agent/server.ts diff --git a/samples/typescript/src/roles/merchant-payment-processor-agent/tools.ts b/code/samples/typescript/src/roles/merchant-payment-processor-agent/tools.ts similarity index 100% rename from samples/typescript/src/roles/merchant-payment-processor-agent/tools.ts rename to code/samples/typescript/src/roles/merchant-payment-processor-agent/tools.ts diff --git a/samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts b/code/samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts similarity index 100% rename from samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts rename to code/samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts diff --git a/samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts b/code/samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts similarity index 100% rename from samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts rename to code/samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts diff --git a/samples/typescript/src/roles/shopping-agent-v2/agent.ts b/code/samples/typescript/src/roles/shopping-agent-v2/agent.ts similarity index 98% rename from samples/typescript/src/roles/shopping-agent-v2/agent.ts rename to code/samples/typescript/src/roles/shopping-agent-v2/agent.ts index 6ddccd83..c9f7da73 100644 --- a/samples/typescript/src/roles/shopping-agent-v2/agent.ts +++ b/code/samples/typescript/src/roles/shopping-agent-v2/agent.ts @@ -20,7 +20,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { z, type ZodTypeAny } from 'zod'; +import { z } from 'zod'; import { assembleAndSignMandatesTool, @@ -48,11 +48,11 @@ async function makeMcpClient(serverEntry: string): Promise { return client; } -function mcpTool( +function mcpTool( client: Client, toolName: string, description: string, - parameters: S, + parameters: z.ZodObject, ): FunctionTool { return new FunctionTool({ name: toolName, diff --git a/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts b/code/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts similarity index 100% rename from samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts rename to code/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts diff --git a/samples/typescript/src/roles/shopping-agent-v2/server.ts b/code/samples/typescript/src/roles/shopping-agent-v2/server.ts similarity index 100% rename from samples/typescript/src/roles/shopping-agent-v2/server.ts rename to code/samples/typescript/src/roles/shopping-agent-v2/server.ts diff --git a/samples/typescript/src/roles/shopping-agent/agent.ts b/code/samples/typescript/src/roles/shopping-agent/agent.ts similarity index 97% rename from samples/typescript/src/roles/shopping-agent/agent.ts rename to code/samples/typescript/src/roles/shopping-agent/agent.ts index 1eafdbd0..e742fd55 100644 --- a/samples/typescript/src/roles/shopping-agent/agent.ts +++ b/code/samples/typescript/src/roles/shopping-agent/agent.ts @@ -28,8 +28,8 @@ import { // Import sub_agents import { shopperAgent } from './subagents/shopper/agent.js'; -import { shippingCollectorAgent } from './subagents/shipping-collector/agent.js'; -import { paymentCollectorAgent } from './subagents/payment-collector/agent.js'; +import { shippingCollectorAgent } from './subagents/shipping-address-collector/agent.js'; +import { paymentCollectorAgent } from './subagents/payment-method-collector/agent.js'; /** * Shopping Agent (ADK) diff --git a/samples/typescript/src/roles/shopping-agent/server.ts b/code/samples/typescript/src/roles/shopping-agent/server.ts similarity index 100% rename from samples/typescript/src/roles/shopping-agent/server.ts rename to code/samples/typescript/src/roles/shopping-agent/server.ts diff --git a/samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/agent.ts b/code/samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/agent.ts similarity index 100% rename from samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/agent.ts rename to code/samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/agent.ts diff --git a/samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/tools.ts b/code/samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/tools.ts similarity index 100% rename from samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/tools.ts rename to code/samples/typescript/src/roles/shopping-agent/subagents/payment-method-collector/tools.ts diff --git a/samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/agent.ts b/code/samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/agent.ts similarity index 100% rename from samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/agent.ts rename to code/samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/agent.ts diff --git a/samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/tools.ts b/code/samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/tools.ts similarity index 100% rename from samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/tools.ts rename to code/samples/typescript/src/roles/shopping-agent/subagents/shipping-address-collector/tools.ts diff --git a/samples/typescript/src/roles/shopping-agent/subagents/shopper/agent.ts b/code/samples/typescript/src/roles/shopping-agent/subagents/shopper/agent.ts similarity index 100% rename from samples/typescript/src/roles/shopping-agent/subagents/shopper/agent.ts rename to code/samples/typescript/src/roles/shopping-agent/subagents/shopper/agent.ts diff --git a/samples/typescript/src/roles/shopping-agent/subagents/shopper/tools.ts b/code/samples/typescript/src/roles/shopping-agent/subagents/shopper/tools.ts similarity index 100% rename from samples/typescript/src/roles/shopping-agent/subagents/shopper/tools.ts rename to code/samples/typescript/src/roles/shopping-agent/subagents/shopper/tools.ts diff --git a/samples/typescript/src/roles/shopping-agent/tools.ts b/code/samples/typescript/src/roles/shopping-agent/tools.ts similarity index 100% rename from samples/typescript/src/roles/shopping-agent/tools.ts rename to code/samples/typescript/src/roles/shopping-agent/tools.ts diff --git a/samples/typescript/test/e2e/connectivity.test.ts b/code/samples/typescript/test/e2e/connectivity.test.ts similarity index 100% rename from samples/typescript/test/e2e/connectivity.test.ts rename to code/samples/typescript/test/e2e/connectivity.test.ts diff --git a/samples/typescript/test/e2e/payment-flow.test.ts b/code/samples/typescript/test/e2e/payment-flow.test.ts similarity index 100% rename from samples/typescript/test/e2e/payment-flow.test.ts rename to code/samples/typescript/test/e2e/payment-flow.test.ts diff --git a/samples/typescript/tsconfig.json b/code/samples/typescript/tsconfig.json similarity index 100% rename from samples/typescript/tsconfig.json rename to code/samples/typescript/tsconfig.json diff --git a/samples/typescript/vitest.config.ts b/code/samples/typescript/vitest.config.ts similarity index 100% rename from samples/typescript/vitest.config.ts rename to code/samples/typescript/vitest.config.ts From 93fe2e3171221dac011383668970a19a26111fa8 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 30 May 2026 20:50:12 -0500 Subject: [PATCH 04/17] feat(typescript): migrate HNP/credentials flow to real SD-JWT with hash binding + constraints Replaces the W3C VC / stubbed crypto with a real SD-JWT (ES256) mandate chain mirroring the Python AP2 v0.2 model, and hardens the highest-impact verification gaps. SD-JWT core (src/common/sdjwt/): - crypto.ts: ES256 keypair/signer/verifier/hasher/salt (WebCrypto via @sd-jwt/crypto-nodejs) - mandate-sdjwt.ts: issueOpenMandate (cnf.jwk holder binding), presentClosedMandate (KB-SD-JWT), verifyMandate (issuer sig + KB against cnf + nonce) - jwt.ts: plain ES256 compact JWS for the checkout JWT and PSP receipt - key-store.ts: file-backed ES256 keypairs shared across agent + MCP subprocesses - constraints.ts: amount + currency + merchant-allowlist checker - removes the @digitalbazaar W3C VC stack (src/common/vc/ deleted) Roles: - credentials-provider-agent: payment token is now an SD-JWT (account-manager), same string-in/string-out interface (tools.ts untouched) - credentials-provider-mcp: issue_payment_credential verifies the presented payment mandate is holder-bound to the SPECIFIC checkout_jwt_hash (replay protection); verify_payment_receipt verifies the PSP receipt signature - merchant-agent-mcp: create_checkout signs a real ES256 checkout JWT and commits to open_checkout_hash; complete_checkout verifies it, settles with the PSP, returns the PSP-signed payment receipt; carts are file-backed in TEMP_DB - merchant-payment-processor-mcp: signs the payment receipt with the PSP key - shopping-agent-v2: consent->monitoring->purchase LlmAgent hierarchy via subAgents + MCPToolset (ADK-TS idiomatic) with afterToolCallback error escalation; mandate-tools now do real SD-JWT issue/present/verify, real checkConstraintsAgainstMandate, payment presentation bound to checkout_jwt_hash, and a resetTempDb tool Deps: + @sd-jwt/core, @sd-jwt/crypto-nodejs, @modelcontextprotocol/sdk@^1.29; @google/adk -> 1.1.0; zod -> 4.4.3; - @digitalbazaar/*. Tests (16, all passing): SD-JWT issue/verify/KB round-trip + forged-KB rejection, credentials-provider SD-JWT token round-trip, ES256 JWT sign/verify/tamper, constraint checker, and hash-binding replay protection. tsc + eslint clean. Verified end-to-end live: intent -> signed open mandates -> price-drop trigger -> autonomous purchase with merchant- and PSP-signed receipts sharing one checkout_jwt_hash. Known gaps vs Python v0.2 (intentional): x402 stack not ported; VI's full multi-hop/multi-pair chain verifier (chain.py) not replicated. --- code/samples/typescript/package-lock.json | 440 +++++++----------- code/samples/typescript/package.json | 13 +- .../src/common/sdjwt/constraints.ts | 76 +++ .../typescript/src/common/sdjwt/crypto.ts | 45 ++ .../typescript/src/common/sdjwt/index.ts | 17 + .../typescript/src/common/sdjwt/jwt.ts | 59 +++ .../typescript/src/common/sdjwt/key-store.ts | 43 ++ .../src/common/sdjwt/mandate-sdjwt.ts | 155 ++++++ .../typescript/src/common/vc/ap2-context.ts | 50 -- .../typescript/src/common/vc/credentials.ts | 183 -------- .../src/common/vc/document-loader.ts | 118 ----- .../samples/typescript/src/common/vc/index.ts | 65 --- .../typescript/src/common/vc/key-manager.ts | 127 ----- .../samples/typescript/src/common/vc/types.ts | 113 ----- .../account-manager.ts | 159 +++++-- .../credentials-provider-agent/server.ts | 8 +- .../roles/credentials-provider-mcp/server.ts | 159 +++++-- .../src/roles/merchant-agent-mcp/server.ts | 305 ++++++++---- .../merchant-payment-processor-mcp/server.ts | 81 ++-- .../trigger-server.ts | 26 +- .../src/roles/shopping-agent-v2/agent.ts | 288 ++++++------ .../roles/shopping-agent-v2/mandate-tools.ts | 211 +++++++-- .../shopping-agent-v2/prompts/consent.ts | 44 ++ .../shopping-agent-v2/prompts/monitoring.ts | 26 ++ .../shopping-agent-v2/prompts/purchase.ts | 35 ++ .../test/unit/constraints-and-binding.test.ts | 92 ++++ .../test/unit/credentials-token.test.ts | 68 +++ .../typescript/test/unit/es256-jwt.test.ts | 42 ++ .../test/unit/sdjwt-mandate.test.ts | 125 +++++ 29 files changed, 1828 insertions(+), 1345 deletions(-) create mode 100644 code/samples/typescript/src/common/sdjwt/constraints.ts create mode 100644 code/samples/typescript/src/common/sdjwt/crypto.ts create mode 100644 code/samples/typescript/src/common/sdjwt/index.ts create mode 100644 code/samples/typescript/src/common/sdjwt/jwt.ts create mode 100644 code/samples/typescript/src/common/sdjwt/key-store.ts create mode 100644 code/samples/typescript/src/common/sdjwt/mandate-sdjwt.ts delete mode 100644 code/samples/typescript/src/common/vc/ap2-context.ts delete mode 100644 code/samples/typescript/src/common/vc/credentials.ts delete mode 100644 code/samples/typescript/src/common/vc/document-loader.ts delete mode 100644 code/samples/typescript/src/common/vc/index.ts delete mode 100644 code/samples/typescript/src/common/vc/key-manager.ts delete mode 100644 code/samples/typescript/src/common/vc/types.ts create mode 100644 code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts create mode 100644 code/samples/typescript/src/roles/shopping-agent-v2/prompts/monitoring.ts create mode 100644 code/samples/typescript/src/roles/shopping-agent-v2/prompts/purchase.ts create mode 100644 code/samples/typescript/test/unit/constraints-and-binding.test.ts create mode 100644 code/samples/typescript/test/unit/credentials-token.test.ts create mode 100644 code/samples/typescript/test/unit/es256-jwt.test.ts create mode 100644 code/samples/typescript/test/unit/sdjwt-mandate.test.ts diff --git a/code/samples/typescript/package-lock.json b/code/samples/typescript/package-lock.json index 65771767..d2ff5822 100644 --- a/code/samples/typescript/package-lock.json +++ b/code/samples/typescript/package-lock.json @@ -10,15 +10,12 @@ "license": "Apache-2.0", "dependencies": { "@a2a-js/sdk": "^0.3.13", - "@digitalbazaar/ed25519-signature-2020": "^5.4.0", - "@digitalbazaar/ed25519-verification-key-2020": "^4.2.0", - "@digitalbazaar/vc": "^7.3.0", "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", "@google-cloud/storage": "^7.19.0", - "@google/adk": "^0.6.1", + "@google/adk": "^1.1.0", "@google/genai": "^1.37.0", - "@modelcontextprotocol/sdk": "^1.0.0", + "@modelcontextprotocol/sdk": "^1.29.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.205.0", "@opentelemetry/core": "^2.1.0", @@ -32,14 +29,16 @@ "@opentelemetry/sdk-node": "^0.205.0", "@opentelemetry/sdk-trace-base": "^2.1.0", "@opentelemetry/sdk-trace-node": "^2.1.0", + "@sd-jwt/core": "^0.19.0", + "@sd-jwt/crypto-nodejs": "^0.19.0", "dotenv": "^17.4.2", "express": "^5.1.0", "uuid": "^13.0.0", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "devDependencies": { "@eslint/js": "^9.39.4", - "@google/adk-devtools": "^0.6.1", + "@google/adk-devtools": "^1.1.0", "@types/express": "^5.0.6", "@types/node": "^25.0.10", "@types/uuid": "^10.0.0", @@ -412,90 +411,6 @@ "kuler": "^2.0.0" } }, - "node_modules/@digitalbazaar/credentials-context": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@digitalbazaar/credentials-context/-/credentials-context-3.2.0.tgz", - "integrity": "sha512-WruXZAF182pCYq/z2JPJ4N4mVDu6pd/hr7widdCqbKekA53BXHoX7XhL9tmRmQ8kfmkaStBfla67QHhkchWYBQ==", - "license": "SEE LICENSE IN LICENSE.md" - }, - "node_modules/@digitalbazaar/ed25519-multikey": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@digitalbazaar/ed25519-multikey/-/ed25519-multikey-1.3.1.tgz", - "integrity": "sha512-55qIbOaAyswVCFfZ70ap7SN2bDSwYmcVbUtGCrpVF/CjuJ8IPqf6z8fDPJzB+CE7Q896SaZlcDukz4LOwIRCPA==", - "license": "BSD-3-Clause", - "dependencies": { - "@noble/ed25519": "^1.6.0", - "base58-universal": "^2.0.0", - "base64url-universal": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@digitalbazaar/ed25519-signature-2020": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@digitalbazaar/ed25519-signature-2020/-/ed25519-signature-2020-5.4.0.tgz", - "integrity": "sha512-dHjOv41wiKLoPaE1S9g4NCMKNnelYtBa5fCJKwrqo+cPlLuKcEhtD7w/uuc5non7bf/GX5sa5YfDI/sRUOS6iw==", - "license": "BSD-3-Clause", - "dependencies": { - "@digitalbazaar/ed25519-multikey": "^1.1.0", - "@digitalbazaar/ed25519-verification-key-2020": "^4.1.0", - "base58-universal": "^2.0.0", - "ed25519-signature-2020-context": "^1.1.0", - "jsonld-signatures": "^11.3.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@digitalbazaar/ed25519-verification-key-2020": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@digitalbazaar/ed25519-verification-key-2020/-/ed25519-verification-key-2020-4.2.0.tgz", - "integrity": "sha512-urEVTYkt+uYD8GjdoS6gkm2sKBich1hqx42b6vvUOmNgF0agZ95JlUjiJXEx+VOwu7WSjJSnEBph2Qatkrk1CA==", - "license": "BSD-3-Clause", - "dependencies": { - "@noble/ed25519": "^1.6.0", - "base58-universal": "^2.0.0", - "base64url-universal": "^2.0.0", - "crypto-ld": "^7.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@digitalbazaar/http-client": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-4.3.0.tgz", - "integrity": "sha512-6lMpxpt9BOmqHKGs9Xm6DP4LlZTBFer/ZjHvP3FcW3IaUWYIWC7dw5RFZnvw4fP57kAVcm1dp3IF+Y50qhBvAw==", - "license": "BSD-3-Clause", - "dependencies": { - "ky": "^1.14.2", - "undici": "^6.23.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@digitalbazaar/security-context": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@digitalbazaar/security-context/-/security-context-1.0.1.tgz", - "integrity": "sha512-0WZa6tPiTZZF8leBtQgYAfXQePFQp2z5ivpCEN/iZguYYZ0TB9qRmWtan5XH6mNFuusHtMcyIzAcReyE6rZPhA==", - "license": "BSD-3-Clause" - }, - "node_modules/@digitalbazaar/vc": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@digitalbazaar/vc/-/vc-7.3.0.tgz", - "integrity": "sha512-liPd4fCQYGXB3aTUY7XNt2A41EGwwdCJdqQvOjXp1ySwLptvifOYA/EvlViJfFgjDeU5DaSEeB+2V1/BY6oMHA==", - "license": "BSD-3-Clause", - "dependencies": { - "@digitalbazaar/credentials-context": "^3.2.0", - "jsonld": "^9.0.0", - "jsonld-signatures": "^11.6.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@emnapi/core": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", @@ -1354,54 +1269,56 @@ } }, "node_modules/@google/adk": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@google/adk/-/adk-0.6.1.tgz", - "integrity": "sha512-AHWWM5pEOaZCZq4MASFbnnm5v6L71rt5LRt4qcnyJBjltCTgLevEj7VDGSYNe3FExsSVFyMmc9/1FYpbkjYzAw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@google/adk/-/adk-1.1.0.tgz", + "integrity": "sha512-uB6ieMtif2hHsvTMB4WgGaYbwiK5tDDpm0R5pCdruUtMk+TTPDgJnVm8cpkXpOsutuEX5kg+1H6vQlw/CPqgfg==", "license": "Apache-2.0", "dependencies": { "@a2a-js/sdk": "^0.3.10", + "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", + "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", + "@google-cloud/storage": "^7.17.1", "@google/genai": "^1.37.0", "@mikro-orm/core": "^6.6.10", "@mikro-orm/reflection": "^6.6.6", "@modelcontextprotocol/sdk": "^1.26.0", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/api-logs": "^0.205.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.205.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.205.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.205.0", + "@opentelemetry/resource-detector-gcp": "^0.40.0", + "@opentelemetry/resources": "^2.1.0", + "@opentelemetry/sdk-logs": "^0.205.0", + "@opentelemetry/sdk-metrics": "^2.1.0", + "@opentelemetry/sdk-trace-base": "^2.1.0", + "@opentelemetry/sdk-trace-node": "^2.1.0", "express": "^4.22.1", "google-auth-library": "^10.3.0", - "lodash-es": "^4.17.23", + "js-yaml": "^4.1.1", + "jsonpath-plus": "^10.4.0", + "lodash-es": "^4.18.1", "winston": "^3.19.0", "zod": "^4.2.1", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { - "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", - "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", - "@google-cloud/storage": "^7.17.1", "@mikro-orm/mariadb": "^6.6.6", "@mikro-orm/mssql": "^6.6.6", "@mikro-orm/mysql": "^6.6.6", "@mikro-orm/postgresql": "^6.6.6", - "@mikro-orm/sqlite": "^6.6.6", - "@opentelemetry/api": "1.9.0", - "@opentelemetry/api-logs": "^0.205.0", - "@opentelemetry/exporter-logs-otlp-http": "^0.205.0", - "@opentelemetry/exporter-metrics-otlp-http": "^0.205.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.205.0", - "@opentelemetry/resource-detector-gcp": "^0.40.0", - "@opentelemetry/resources": "^2.1.0", - "@opentelemetry/sdk-logs": "^0.205.0", - "@opentelemetry/sdk-metrics": "^2.1.0", - "@opentelemetry/sdk-trace-base": "^2.1.0", - "@opentelemetry/sdk-trace-node": "^2.1.0" + "@mikro-orm/sqlite": "^6.6.6" } }, "node_modules/@google/adk-devtools": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@google/adk-devtools/-/adk-devtools-0.6.1.tgz", - "integrity": "sha512-0ImIs+JLHS5mBGklcecT1cptYeBFAWYQmAPN+OFwEbneRkEbGp4MksBBbXtLuiw33Wr888aE2+mE4H9VRunHTw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@google/adk-devtools/-/adk-devtools-1.1.0.tgz", + "integrity": "sha512-td2yXcexJi2FRxbCQLFekry+y3Idl5oM3MpWmZVMO3qyhMCc5z1W50ShYF5UxQgZQrp/JHy9+nma41Z5eGBBrg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@clack/prompts": "^0.11.0", - "@google/adk": "^0.6.1", + "@google/adk": "^1.1.0", "@mikro-orm/mariadb": "^6.6.6", "@mikro-orm/mssql": "^6.6.6", "@mikro-orm/mysql": "^6.6.6", @@ -1415,13 +1332,13 @@ "esbuild-shim-plugin": "^1.0.3", "express": "^4.21.2", "fast-glob": "^3.3.3", + "js-yaml": "^4.1.1", "ts-graphviz": "^1.0.1", "winston": "^3.19.0", - "yaml": "^2.8.3", "zod": "^4.2.1" }, "bin": { - "adk": "dist/cli_entrypoint.mjs" + "adk": "dist/esm/cli_entrypoint.js" } }, "node_modules/@google/adk-devtools/node_modules/accepts": { @@ -2337,6 +2254,30 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, "node_modules/@mikro-orm/core": { "version": "6.6.13", "resolved": "https://registry.npmjs.org/@mikro-orm/core/-/core-6.6.13.tgz", @@ -2556,18 +2497,6 @@ "@tybys/wasm-util": "^0.10.0" } }, - "node_modules/@noble/ed25519": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.5.tgz", - "integrity": "sha512-xuS0nwRMQBvSxDa7UxMb61xTiH3MxTgUfhyPUALVIe0FlOAz4sjELwyDRyUvqeEYfRSG9qNjFIycqLZppg4RSA==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" - }, "node_modules/@nodable/entities": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-1.1.0.tgz", @@ -4221,6 +4150,79 @@ "dev": true, "license": "MIT" }, + "node_modules/@sd-jwt/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@sd-jwt/core/-/core-0.19.0.tgz", + "integrity": "sha512-/FeuQjzQFtnxDsHF64Bw+3uixVogNkXlMGBo1CKWrxB/OLNqtGXQZplKBRZMJWgnOJEqDwD6750wrOxsVly6mg==", + "license": "Apache-2.0", + "dependencies": { + "@sd-jwt/decode": "0.19.0", + "@sd-jwt/present": "0.19.0", + "@sd-jwt/types": "0.19.0", + "@sd-jwt/utils": "0.19.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sd-jwt/crypto-nodejs": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@sd-jwt/crypto-nodejs/-/crypto-nodejs-0.19.0.tgz", + "integrity": "sha512-mRUZ5pgMBqisXIJyq9vzNH0p4szHV7Dg87Mzm7mI7pgPjIkx9g8R1gRBWaRNzy6mblrTvEpcziPe6bnTY04Lqw==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@sd-jwt/decode": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@sd-jwt/decode/-/decode-0.19.0.tgz", + "integrity": "sha512-gRfpJseFRy3bFMdlmJjVjuEcLNuekpiZJD2F2luJIKVlk26AEjZSJf6377vwNySa8hb+i4MZDwdy14lcTTmqtA==", + "license": "Apache-2.0", + "dependencies": { + "@sd-jwt/types": "0.19.0", + "@sd-jwt/utils": "0.19.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sd-jwt/present": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@sd-jwt/present/-/present-0.19.0.tgz", + "integrity": "sha512-WXDwqwUXmtyj7YZ5c+wb26ZBeVOsKXCbCI7s1pRH9ngIjFNDGgAZoVCOmLq8pPgWSJzOTgJe3ErO2k63ZwhyeQ==", + "license": "Apache-2.0", + "dependencies": { + "@sd-jwt/decode": "0.19.0", + "@sd-jwt/types": "0.19.0", + "@sd-jwt/utils": "0.19.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sd-jwt/types": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@sd-jwt/types/-/types-0.19.0.tgz", + "integrity": "sha512-nfuC9zRLKe7o4HSvc+N4ojWRAxo4JGfgcNWpR7bJloLUlnE9eQuu9h9pEaJZht7KRwMpGorNTIdYpoi1btuiew==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@sd-jwt/utils": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@sd-jwt/utils/-/utils-0.19.0.tgz", + "integrity": "sha512-bDwDRjfxMBNOsAXY8q8hnxQq7jdOWxrdqTK926Mxt8DN+ttXbXbZIPLwSh84M90WP0V7+WdkXlZD31iISzUR3w==", + "license": "Apache-2.0", + "dependencies": { + "@sd-jwt/types": "0.19.0", + "js-base64": "^3.7.8" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@so-ric/colorspace": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", @@ -5261,7 +5263,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { @@ -5485,15 +5486,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/base58-universal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base58-universal/-/base58-universal-2.0.0.tgz", - "integrity": "sha512-BgkgF8zVLOAygszG4W8NkLm7iXrw80VYAOcedrzANrIhS14+4W6zVqjyGTFUBM/FpqkHUt8aAYd4DbBBfn3zKg==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=14" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5514,27 +5506,6 @@ ], "license": "MIT" }, - "node_modules/base64url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", - "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/base64url-universal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64url-universal/-/base64url-universal-2.0.0.tgz", - "integrity": "sha512-6Hpg7EBf3t148C3+fMzjf+CHnADVDafWzlJUXAqqqbm4MKNXbsoPdOkWeRTjNlkYG7TpyjIpRO1Gk0SnsFD1rw==", - "license": "BSD-3-Clause", - "dependencies": { - "base64url": "^3.0.1" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -5782,15 +5753,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/canonicalize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.1.0.tgz", - "integrity": "sha512-F705O3xrsUtgt98j7leetNhTWPe+5S72rlL5O4jA1pKqBVQ/dT1O1D6PFxmSXvc0SUOinWS57DKx0I3CHrXJHQ==", - "license": "Apache-2.0", - "bin": { - "canonicalize": "bin/canonicalize.js" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -6104,15 +6066,6 @@ "node": ">= 8" } }, - "node_modules/crypto-ld": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/crypto-ld/-/crypto-ld-7.0.0.tgz", - "integrity": "sha512-RrXy6aB0TOhSiqsgavTQt1G8mKomKIaNLb2JZxj7A/Vi0EwmXguuBQoeiAvePfK6bDR3uQbqYnaLLs4irTWwgw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=14" - } - }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -6431,12 +6384,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/ed25519-signature-2020-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ed25519-signature-2020-context/-/ed25519-signature-2020-context-1.1.0.tgz", - "integrity": "sha512-dBGSmoUIK6h2vadDctrDnhhTO01PR2hJk0mRNEfrRDPCjaIwrfy4J+eziEQ9Q1m8By4f/CSRgKM1h53ydKfdNg==", - "license": "BSD-3-Clause" - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -9046,6 +8993,12 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, "node_modules/js-md4": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", @@ -9056,7 +9009,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9065,6 +9017,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -9125,34 +9086,22 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonld": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-9.0.0.tgz", - "integrity": "sha512-pjMIdkXfC1T2wrX9B9i2uXhGdyCmgec3qgMht+TDj+S0qX3bjWMQUfL7NeqEhuRTi8G5ESzmL9uGlST7nzSEWg==", - "license": "BSD-3-Clause", + "node_modules/jsonpath-plus": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", + "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", + "license": "MIT", "dependencies": { - "@digitalbazaar/http-client": "^4.2.0", - "canonicalize": "^2.1.0", - "lru-cache": "^6.0.0", - "rdf-canonize": "^5.0.0" + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" }, - "engines": { - "node": ">=18" - } - }, - "node_modules/jsonld-signatures": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/jsonld-signatures/-/jsonld-signatures-11.6.0.tgz", - "integrity": "sha512-hzYNZXnfy4cUFf9aiFBtduUz+cknbfBLWtTKvoqVyP2ECPwqfsfkHWFlhccWfAKV/LJkPLyKZRwC1B4T5LO4ZQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@digitalbazaar/security-context": "^1.0.0", - "jsonld": "^9.0.0", - "rdf-canonize": "^5.0.0", - "serialize-error": "^8.1.0" + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, "node_modules/jsonwebtoken": { @@ -9312,18 +9261,6 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, - "node_modules/ky": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", - "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -9710,6 +9647,7 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "license": "ISC", + "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -11214,18 +11152,6 @@ "node": ">=0.10.0" } }, - "node_modules/rdf-canonize": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-5.0.0.tgz", - "integrity": "sha512-g8OUrgMXAR9ys/ZuJVfBr05sPPoMA7nHIVs8VEvg9QwM5W4GR2qSFEEHjsyHF1eWlBaf8Ev40WNjQFQ+nJTO3w==", - "license": "BSD-3-Clause", - "dependencies": { - "setimmediate": "^1.0.5" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -11693,21 +11619,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", @@ -11783,12 +11694,6 @@ "node": ">= 0.4" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -13306,18 +13211,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -13467,15 +13360,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undici": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", - "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, "node_modules/undici-types": { "version": "7.19.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", @@ -14619,6 +14503,8 @@ "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", + "optional": true, + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -14669,9 +14555,9 @@ } }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/code/samples/typescript/package.json b/code/samples/typescript/package.json index 9176d80f..7254b397 100644 --- a/code/samples/typescript/package.json +++ b/code/samples/typescript/package.json @@ -26,15 +26,12 @@ }, "dependencies": { "@a2a-js/sdk": "^0.3.13", - "@digitalbazaar/ed25519-signature-2020": "^5.4.0", - "@digitalbazaar/ed25519-verification-key-2020": "^4.2.0", - "@digitalbazaar/vc": "^7.3.0", "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", "@google-cloud/storage": "^7.19.0", - "@google/adk": "^0.6.1", + "@google/adk": "^1.1.0", "@google/genai": "^1.37.0", - "@modelcontextprotocol/sdk": "^1.0.0", + "@modelcontextprotocol/sdk": "^1.29.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.205.0", "@opentelemetry/core": "^2.1.0", @@ -48,14 +45,16 @@ "@opentelemetry/sdk-node": "^0.205.0", "@opentelemetry/sdk-trace-base": "^2.1.0", "@opentelemetry/sdk-trace-node": "^2.1.0", + "@sd-jwt/core": "^0.19.0", + "@sd-jwt/crypto-nodejs": "^0.19.0", "dotenv": "^17.4.2", "express": "^5.1.0", "uuid": "^13.0.0", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "devDependencies": { "@eslint/js": "^9.39.4", - "@google/adk-devtools": "^0.6.1", + "@google/adk-devtools": "^1.1.0", "@types/express": "^5.0.6", "@types/node": "^25.0.10", "@types/uuid": "^10.0.0", diff --git a/code/samples/typescript/src/common/sdjwt/constraints.ts b/code/samples/typescript/src/common/sdjwt/constraints.ts new file mode 100644 index 00000000..e74c84a8 --- /dev/null +++ b/code/samples/typescript/src/common/sdjwt/constraints.ts @@ -0,0 +1,76 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Minimal mandate-constraint checker (amount + merchant allowlist), a TS + * subset of the Python ap2 constraint engine. The open mandate carries the + * constraints; this module checks an observed price/merchant against them. + */ + +export interface ObservedTransaction { + /** Observed unit price, in the same units as the constraint cap (USD dollars here). */ + price: number; + currency?: string; + /** Merchant identifier/name for the observed offer, if known. */ + merchant?: string; +} + +export interface MandateConstraints { + /** Maximum acceptable price (inclusive), in the same units as the observed price. */ + priceCap?: number; + currency?: string; + /** If non-empty, the merchant of the offer must be one of these. */ + allowedMerchants?: string[]; +} + +export interface ConstraintResult { + meetsConstraints: boolean; + violations: string[]; +} + +/** Check an observed transaction against the open-mandate constraints. */ +export function checkConstraints( + observed: ObservedTransaction, + constraints: MandateConstraints, +): ConstraintResult { + const violations: string[] = []; + + // Amount bound (inclusive). + if (constraints.priceCap !== undefined && observed.price > constraints.priceCap) { + violations.push( + `price ${observed.price} exceeds cap ${constraints.priceCap}`, + ); + } + + // Currency match (only when both sides specify one). + if ( + constraints.currency && + observed.currency && + constraints.currency !== observed.currency + ) { + violations.push( + `currency ${observed.currency} != allowed ${constraints.currency}`, + ); + } + + // Merchant allowlist (only enforced when the mandate restricts merchants). + if (constraints.allowedMerchants && constraints.allowedMerchants.length > 0) { + const merchant = observed.merchant; + if (!merchant) { + violations.push('merchant unknown but mandate restricts merchants'); + } else if ( + !constraints.allowedMerchants.some( + (m) => m.toLowerCase() === merchant.toLowerCase(), + ) + ) { + violations.push(`merchant ${merchant} not in allowlist`); + } + } + + return { meetsConstraints: violations.length === 0, violations }; +} diff --git a/code/samples/typescript/src/common/sdjwt/crypto.ts b/code/samples/typescript/src/common/sdjwt/crypto.ts new file mode 100644 index 00000000..1e6e16fc --- /dev/null +++ b/code/samples/typescript/src/common/sdjwt/crypto.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * ES256 crypto primitives for SD-JWT, backed by @sd-jwt/crypto-nodejs + * (WebCrypto under the hood). AP2 v0.2 mandates ES256 (ECDSA P-256) for + * mandate SD-JWTs, so this is the single algorithm used across issue, + * present (key-binding), and verify. + */ + +import { ES256, digest, generateSalt } from '@sd-jwt/crypto-nodejs'; +import type { Hasher, SaltGenerator, Signer, Verifier } from '@sd-jwt/types'; + +/** A P-256 JSON Web Key (public or private). */ +export type Es256Jwk = JsonWebKey; + +export interface Es256KeyPair { + publicKey: Es256Jwk; + privateKey: Es256Jwk; +} + +export const ALG = ES256.alg; // "ES256" + +/** Hasher (sha-256) compatible with @sd-jwt SDJwtInstance config. */ +export const hasher: Hasher = (data, alg) => digest(data, alg); + +/** Salt generator for selective-disclosure commitments. */ +export const saltGenerator: SaltGenerator = (length: number) => generateSalt(length); + +export async function generateKeyPair(): Promise { + return ES256.generateKeyPair(); +} + +export async function makeSigner(privateJwk: Es256Jwk): Promise { + return ES256.getSigner(privateJwk); +} + +export async function makeVerifier(publicJwk: Es256Jwk): Promise { + return ES256.getVerifier(publicJwk); +} diff --git a/code/samples/typescript/src/common/sdjwt/index.ts b/code/samples/typescript/src/common/sdjwt/index.ts new file mode 100644 index 00000000..9e37cd67 --- /dev/null +++ b/code/samples/typescript/src/common/sdjwt/index.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Barrel for the SD-JWT mandate primitives. + */ + +export * from './crypto.js'; +export * from './mandate-sdjwt.js'; +export * from './jwt.js'; +export * from './key-store.js'; +export * from './constraints.js'; diff --git a/code/samples/typescript/src/common/sdjwt/jwt.ts b/code/samples/typescript/src/common/sdjwt/jwt.ts new file mode 100644 index 00000000..24547345 --- /dev/null +++ b/code/samples/typescript/src/common/sdjwt/jwt.ts @@ -0,0 +1,59 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Plain ES256 compact JWS (JWT) helpers — distinct from the SD-JWT mandate + * format. Used for artifacts that are signed but not selectively disclosed: + * the merchant-signed checkout JWT and the PSP-signed payment receipt. + * + * Built directly on the ES256 signer/verifier from crypto.ts (ECDSA P-256), + * producing a standard `..` + * compact JWS. + */ + +import { ALG, makeSigner, makeVerifier, type Es256Jwk } from './crypto.js'; + +function b64urlJson(value: unknown): string { + return Buffer.from(JSON.stringify(value)).toString('base64url'); +} + +/** Sign a payload as a compact ES256 JWS. */ +export async function signJwtEs256( + payload: Record, + privateJwk: Es256Jwk, +): Promise { + const signer = await makeSigner(privateJwk); + const header = { alg: ALG, typ: 'JWT' }; + const signingInput = `${b64urlJson(header)}.${b64urlJson(payload)}`; + const signature = await signer(signingInput); + return `${signingInput}.${signature}`; +} + +/** + * Verify a compact ES256 JWS and return its payload. + * @throws if the token is malformed or the signature does not verify. + */ +export async function verifyJwtEs256( + compact: string, + publicJwk: Es256Jwk, +): Promise> { + const parts = compact.split('.'); + if (parts.length !== 3) { + throw new Error('malformed JWT: expected three dot-separated segments'); + } + const [headerB64, payloadB64, signature] = parts; + const verifier = await makeVerifier(publicJwk); + const ok = await verifier(`${headerB64}.${payloadB64}`, signature); + if (!ok) { + throw new Error('invalid JWT signature'); + } + return JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf-8')) as Record< + string, + unknown + >; +} diff --git a/code/samples/typescript/src/common/sdjwt/key-store.ts b/code/samples/typescript/src/common/sdjwt/key-store.ts new file mode 100644 index 00000000..233f95ab --- /dev/null +++ b/code/samples/typescript/src/common/sdjwt/key-store.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * File-backed ES256 keypair store. Each role (user, agent, merchant, psp) + * persists its keypair as `_key.jwk.json` under a shared TEMP_DB so that + * separate processes (the agent and each MCP subprocess) can resolve the same + * public keys and verify each other's signatures. + */ + +import fs from 'node:fs'; +import path from 'node:path'; +import { generateKeyPair, type Es256Jwk, type Es256KeyPair } from './crypto.js'; + +function keyPath(tempDb: string, name: string): string { + return path.join(tempDb, `${name}_key.jwk.json`); +} + +/** Load the named keypair, generating + persisting it on first use. */ +export async function loadOrCreateKeyPair(tempDb: string, name: string): Promise { + try { + return JSON.parse(fs.readFileSync(keyPath(tempDb, name), 'utf-8')) as Es256KeyPair; + } catch { + const pair = await generateKeyPair(); + fs.mkdirSync(tempDb, { recursive: true }); + fs.writeFileSync(keyPath(tempDb, name), JSON.stringify(pair)); + return pair; + } +} + +/** Read just the public JWK of a previously persisted keypair (null if absent). */ +export function loadPublicJwk(tempDb: string, name: string): Es256Jwk | null { + try { + return (JSON.parse(fs.readFileSync(keyPath(tempDb, name), 'utf-8')) as Es256KeyPair).publicKey; + } catch { + return null; + } +} diff --git a/code/samples/typescript/src/common/sdjwt/mandate-sdjwt.ts b/code/samples/typescript/src/common/sdjwt/mandate-sdjwt.ts new file mode 100644 index 00000000..8fb77af7 --- /dev/null +++ b/code/samples/typescript/src/common/sdjwt/mandate-sdjwt.ts @@ -0,0 +1,155 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Real SD-JWT mandate primitives (replacing the earlier stubs), built on + * @sd-jwt/core. This mirrors the Python AP2 v0.2 reference at + * code/sdk/python/ap2/sdk/sdjwt/ (sd_jwt.py + kb_sd_jwt.py): + * + * - issueOpenMandate: issuer signs an SD-JWT with selectively-disclosable + * claims and a `cnf.jwk` holder-binding key (RFC 7800). + * - presentClosedMandate: the holder produces a KB-SD-JWT (Key-Binding JWT) + * proving possession of the `cnf` key, bound to a + * nonce + audience (the open->closed delegation hop). + * - verifyMandate: verifies the issuer signature and, when a KB-JWT is + * present, verifies it against the `cnf.jwk` extracted + * from the issuer payload — the same cnf-walk the + * Python chain verifier performs. + */ + +import { SDJwtInstance } from '@sd-jwt/core'; +import type { DisclosureFrame, KbVerifier, PresentationFrame } from '@sd-jwt/types'; +import { + ALG, + hasher, + saltGenerator, + makeSigner, + makeVerifier, + type Es256Jwk, +} from './crypto.js'; + +export interface OpenMandateInput { + /** The mandate body (constraints, expiry, description, etc.). */ + claims: Record; + /** Top-level claim keys to make selectively disclosable. */ + disclosable: string[]; + /** Issuer (e.g. user device) private key that signs the mandate. */ + issuerPrivateJwk: Es256Jwk; + /** Holder (e.g. agent) public key bound into `cnf.jwk` for key binding. */ + holderPublicJwk: Es256Jwk; +} + +/** Issue an open mandate as an SD-JWT with a `cnf.jwk` holder-binding key. */ +export async function issueOpenMandate(input: OpenMandateInput): Promise { + const signer = await makeSigner(input.issuerPrivateJwk); + const sdjwt = new SDJwtInstance({ + signer, + signAlg: ALG, + hasher, + hashAlg: 'sha-256', + saltGenerator, + }); + const payload = { + ...input.claims, + // cnf is NEVER selectively disclosed — it must always be present so a + // verifier can key-bind the next hop (RFC 7800). + cnf: { jwk: input.holderPublicJwk }, + }; + const disclosureFrame = { _sd: input.disclosable } as DisclosureFrame; + return sdjwt.issue(payload, disclosureFrame); +} + +export interface PresentInput { + /** The open mandate SD-JWT to present. */ + openMandateSdJwt: string; + /** Disclosable claim keys to reveal in this presentation. */ + disclose: string[]; + /** Holder private key — MUST correspond to the SD-JWT's `cnf.jwk`. */ + holderPrivateJwk: Es256Jwk; + /** Replay-protection nonce supplied by the verifier. */ + nonce: string; + /** Intended audience of the presentation. */ + audience: string; + /** Seconds since epoch for the KB-JWT `iat`. Defaults to now. */ + issuedAt?: number; +} + +/** Produce a KB-SD-JWT: a holder-bound presentation of the open mandate. */ +export async function presentClosedMandate(input: PresentInput): Promise { + const kbSigner = await makeSigner(input.holderPrivateJwk); + const sdjwt = new SDJwtInstance({ + hasher, + hashAlg: 'sha-256', + saltGenerator, + kbSigner, + kbSignAlg: ALG, + }); + const presentationFrame = Object.fromEntries( + input.disclose.map((k) => [k, true]), + ) as PresentationFrame>; + return sdjwt.present(input.openMandateSdJwt, presentationFrame, { + kb: { + payload: { + iat: input.issuedAt ?? Math.floor(Date.now() / 1000), + aud: input.audience, + nonce: input.nonce, + }, + }, + }); +} + +export interface VerifyInput { + mandateSdJwt: string; + /** Issuer public key to check the SD-JWT signature. */ + issuerPublicJwk: Es256Jwk; + /** Required if the presentation carries a KB-JWT. */ + nonce?: string; +} + +export interface VerifyResult { + payload: Record; + /** True when a valid Key-Binding JWT was verified against `cnf.jwk`. */ + keyBound: boolean; +} + +/** Verify the issuer signature and, if present, the KB-JWT against cnf.jwk. */ +export async function verifyMandate(input: VerifyInput): Promise { + const verifier = await makeVerifier(input.issuerPublicJwk); + + // Decode first to pull the holder key out of `cnf.jwk` — exactly what the + // Python chain verifier does to resolve each hop's key. + const decodeInst = new SDJwtInstance({ hasher, hashAlg: 'sha-256' }); + const decoded = await decodeInst.decode(input.mandateSdJwt); + const issuerPayload = (decoded.jwt?.payload ?? {}) as Record; + const cnf = issuerPayload.cnf as { jwk?: Es256Jwk } | undefined; + + let kbVerifier: KbVerifier | undefined; + if (cnf?.jwk) { + const holderVerify = await makeVerifier(cnf.jwk); + kbVerifier = (data: string, sig: string) => holderVerify(data, sig); + } + + const sdjwt = new SDJwtInstance({ + hasher, + hashAlg: 'sha-256', + verifier, + kbVerifier, + }); + + const result = await sdjwt.verify( + input.mandateSdJwt, + input.nonce ? { keyBindingNonce: input.nonce } : undefined, + ); + + return { + payload: result.payload as Record, + keyBound: 'kb' in result && result.kb !== undefined, + }; +} + +export { generateKeyPair } from './crypto.js'; diff --git a/code/samples/typescript/src/common/vc/ap2-context.ts b/code/samples/typescript/src/common/vc/ap2-context.ts deleted file mode 100644 index 902d43b0..00000000 --- a/code/samples/typescript/src/common/vc/ap2-context.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Custom JSON-LD context for AP2 payment credential terms. - * - * This defines the vocabulary for payment method properties used in - * PaymentCredential VCs so that JSON-LD expansion/canonicalization - * can handle them without "safe mode" validation errors. - */ - -export const AP2_CONTEXT_URL = 'https://ap2.example/contexts/payment/v1'; - -/** - * The AP2 payment context document. Maps AP2-specific terms to a - * local vocabulary namespace so they are recognized by JSON-LD processors. - */ -export const ap2PaymentContext = { - '@context': { - '@vocab': 'https://ap2.example/vocab#', - PaymentCredential: 'https://ap2.example/vocab#PaymentCredential', - PaymentMethod: 'https://ap2.example/vocab#PaymentMethod', - alias: 'https://ap2.example/vocab#alias', - network: 'https://ap2.example/vocab#network', - cryptogram: 'https://ap2.example/vocab#cryptogram', - token: 'https://ap2.example/vocab#token', - card_holder_name: 'https://ap2.example/vocab#card_holder_name', - card_expiration: 'https://ap2.example/vocab#card_expiration', - card_billing_address: 'https://ap2.example/vocab#card_billing_address', - account_number: 'https://ap2.example/vocab#account_number', - brand: 'https://ap2.example/vocab#brand', - account_identifier: 'https://ap2.example/vocab#account_identifier', - postal_code: 'https://ap2.example/vocab#postal_code', - country: 'https://ap2.example/vocab#country', - paymentMandateId: 'https://ap2.example/vocab#paymentMandateId', - }, -}; diff --git a/code/samples/typescript/src/common/vc/credentials.ts b/code/samples/typescript/src/common/vc/credentials.ts deleted file mode 100644 index f8da061d..00000000 --- a/code/samples/typescript/src/common/vc/credentials.ts +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * W3C Verifiable Credential issuance and verification utilities. - * - * Uses @digitalbazaar/vc with Ed25519Signature2020 to issue and verify - * payment credential VCs within the AP2 multi-agent system. - */ -import { v4 as uuidv4 } from 'uuid'; -// @ts-expect-error - JS-only module without type declarations -import * as vc from '@digitalbazaar/vc'; -// @ts-expect-error - JS-only module without type declarations -import { Ed25519Signature2020 } from '@digitalbazaar/ed25519-signature-2020'; -import { getKeyManager } from './key-manager.js'; -import { documentLoader } from './document-loader.js'; -import { AP2_CONTEXT_URL } from './ap2-context.js'; -import type { - VerifiableCredential, - VerifiablePresentation, - VerifyCredentialResult, - VerifyPresentationResult, -} from './types.js'; - -const CREDENTIALS_CONTEXT_V1 = 'https://www.w3.org/2018/credentials/v1'; -const ED25519_2020_CONTEXT = 'https://w3id.org/security/suites/ed25519-2020/v1'; - -/** - * Build a signing suite from the current key manager state. - */ -function buildSigningSuite(): InstanceType { - const { keyPair } = getKeyManager(); - return new Ed25519Signature2020({ key: keyPair }); -} - -/** - * Build a verification suite (no private key needed). - */ -function buildVerificationSuite(): InstanceType { - return new Ed25519Signature2020(); -} - -// -- Credential Issuance -- - -export interface IssuePaymentCredentialOptions { - /** The subject holding the credential (e.g., user email or DID). */ - subjectId?: string; - /** The payment method data to embed in the credential. */ - paymentMethod: Record; - /** The payment mandate ID this credential is bound to. */ - paymentMandateId?: string; -} - -/** - * Issue a W3C Verifiable Credential for a payment method. - * - * The credential embeds the payment method data as the credentialSubject - * and is signed by the credentials-provider's Ed25519 key. - * - * @returns A signed VerifiableCredential object. - */ -export async function issuePaymentCredential( - options: IssuePaymentCredentialOptions -): Promise { - const { paymentMethod, subjectId, paymentMandateId } = options; - const { issuerId } = getKeyManager(); - const suite = buildSigningSuite(); - - const credential: Record = { - '@context': [CREDENTIALS_CONTEXT_V1, ED25519_2020_CONTEXT, AP2_CONTEXT_URL], - id: `urn:uuid:${uuidv4()}`, - type: ['VerifiableCredential', 'PaymentCredential'], - issuer: issuerId, - issuanceDate: new Date().toISOString(), - credentialSubject: { - ...(subjectId ? { id: subjectId } : {}), - type: 'PaymentMethod', - ...paymentMethod, - ...(paymentMandateId ? { paymentMandateId } : {}), - }, - }; - - const signedCredential: VerifiableCredential = await vc.issue({ - credential, - suite, - documentLoader, - }); - - return signedCredential; -} - -// -- Credential Verification -- - -/** - * Verify a W3C Verifiable Credential. - * - * Checks the cryptographic signature and validates the credential structure. - * - * @returns The verification result with `verified: true/false`. - */ -export async function verifyCredential( - credential: VerifiableCredential -): Promise { - const suite = buildVerificationSuite(); - - const result: VerifyCredentialResult = await vc.verifyCredential({ - credential, - suite, - documentLoader, - }); - - return result; -} - -/** - * Verify a credential and extract the credentialSubject if valid. - * - * @returns The credential subject data, or throws if verification fails. - */ -export async function verifyAndExtractSubject( - credential: VerifiableCredential -): Promise> { - const result = await verifyCredential(credential); - - if (!result.verified) { - const errorMsg = result.error?.message ?? 'Unknown verification error'; - throw new Error(`Credential verification failed: ${errorMsg}`); - } - - const subject = credential.credentialSubject; - if (Array.isArray(subject)) { - return subject[0] as Record; - } - return subject as Record; -} - -// -- Verifiable Presentations -- - -/** - * Create an unsigned Verifiable Presentation wrapping one or more VCs. - * - * Unsigned presentations are sufficient for the AP2 inter-agent flow - * because the A2A transport layer provides message authenticity. - */ -export function createPresentation( - credentials: VerifiableCredential | VerifiableCredential[], - holder?: string -): VerifiablePresentation { - return vc.createPresentation({ - verifiableCredential: credentials, - holder, - version: 1.0, - }); -} - -/** - * Verify an unsigned Verifiable Presentation and all contained credentials. - */ -export async function verifyPresentation( - presentation: VerifiablePresentation -): Promise { - const suite = buildVerificationSuite(); - - return vc.verify({ - presentation, - suite, - unsignedPresentation: true, - documentLoader, - }); -} diff --git a/code/samples/typescript/src/common/vc/document-loader.ts b/code/samples/typescript/src/common/vc/document-loader.ts deleted file mode 100644 index 2ac2e3b8..00000000 --- a/code/samples/typescript/src/common/vc/document-loader.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Custom document loader for VC operations. - * - * Combines the built-in credential/security contexts from the digitalbazaar - * packages with a local store for issuer controller documents and - * verification method resolution. This avoids any network fetches. - */ -import type { DocumentLoader, DocumentLoaderResult } from './types.js'; - -// @ts-expect-error - JS-only module without type declarations -import { contexts as credentialContexts } from '@digitalbazaar/credentials-context'; -// @ts-expect-error - JS-only module without type declarations -import { contexts as securityContexts } from '@digitalbazaar/security-context'; -// @ts-expect-error - JS-only module without type declarations -import ed25519Context from 'ed25519-signature-2020-context'; -import { AP2_CONTEXT_URL, ap2PaymentContext } from './ap2-context.js'; - -/** - * In-memory store for dynamically registered documents (controller docs, - * verification methods). Populated by the key manager when keys are created. - */ -const localDocuments = new Map(); - -/** Register a document so the loader can resolve it by URL. */ -export function registerDocument(url: string, document: unknown): void { - localDocuments.set(url, document); -} - -/** Remove a registered document. */ -export function unregisterDocument(url: string): void { - localDocuments.delete(url); -} - -/** Clear all registered documents (useful for testing). */ -export function clearDocuments(): void { - localDocuments.clear(); -} - -/** - * Build a static context map from the bundled packages. - */ -function buildStaticContexts(): Map { - const contexts = new Map(); - - // W3C Credentials contexts (v1, v2) - for (const [url, doc] of credentialContexts as Map) { - contexts.set(url, doc); - } - - // Security contexts (v1, v2) - for (const [url, doc] of securityContexts as Map) { - contexts.set(url, doc); - } - - // Ed25519 2020 suite context - for (const [url, doc] of ed25519Context.contexts as Map) { - contexts.set(url, doc); - } - - // AP2 custom payment context - contexts.set(AP2_CONTEXT_URL, ap2PaymentContext); - - return contexts; -} - -const staticContexts = buildStaticContexts(); - -/** - * Document loader that resolves: - * 1. Static W3C/security/Ed25519 contexts from bundled packages - * 2. Dynamically registered local documents (controller docs, keys) - * - * Never makes network requests. - */ -export const documentLoader: DocumentLoader = async ( - url: string -): Promise => { - // Check static contexts first - const staticDoc = staticContexts.get(url); - if (staticDoc !== undefined) { - return { - contextUrl: null, - documentUrl: url, - document: staticDoc, - }; - } - - // Check dynamically registered documents - const localDoc = localDocuments.get(url); - if (localDoc !== undefined) { - return { - contextUrl: null, - documentUrl: url, - document: localDoc, - }; - } - - throw new Error( - `Document loader unable to load URL "${url}". ` + - 'Ensure the document is registered or a known context.' - ); -}; diff --git a/code/samples/typescript/src/common/vc/index.ts b/code/samples/typescript/src/common/vc/index.ts deleted file mode 100644 index d3855089..00000000 --- a/code/samples/typescript/src/common/vc/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * W3C Verifiable Credentials module for the AP2 multi-agent system. - * - * Provides VC issuance, verification, and presentation creation - * using Ed25519Signature2020 signatures. - * - * Usage: - * import { initKeyManager, issuePaymentCredential, verifyCredential } from '../vc/index.js'; - * - * // At startup: - * await initKeyManager(); - * - * // Issue a credential: - * const vc = await issuePaymentCredential({ paymentMethod: { ... } }); - * - * // Verify a credential: - * const result = await verifyCredential(vc); - */ - -export { initKeyManager, getKeyManager, exportKeyPair, resetKeyManager } from './key-manager.js'; - -export { - issuePaymentCredential, - verifyCredential, - verifyAndExtractSubject, - createPresentation, - verifyPresentation, -} from './credentials.js'; - -export type { - IssuePaymentCredentialOptions, -} from './credentials.js'; - -export { - documentLoader, - registerDocument, - unregisterDocument, - clearDocuments, -} from './document-loader.js'; - -export { AP2_CONTEXT_URL } from './ap2-context.js'; - -export type { - VerifiableCredential, - VerifiablePresentation, - VerifyCredentialResult, - VerifyPresentationResult, - DocumentLoader, -} from './types.js'; diff --git a/code/samples/typescript/src/common/vc/key-manager.ts b/code/samples/typescript/src/common/vc/key-manager.ts deleted file mode 100644 index 93db9864..00000000 --- a/code/samples/typescript/src/common/vc/key-manager.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Key pair management for VC operations. - * - * Generates Ed25519 key pairs and registers the corresponding controller - * documents and verification methods with the document loader so that - * issued credentials can be verified offline. - */ -// @ts-expect-error - JS-only module without type declarations -import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020'; -import type { Ed25519KeyPairInstance, Ed25519KeyPairExport } from './types.js'; -import { registerDocument } from './document-loader.js'; - -/** Default issuer DID-like identifier for the credentials provider. */ -const DEFAULT_ISSUER_ID = 'https://ap2.example/issuers/credentials-provider'; - -interface KeyManagerOptions { - /** The issuer identifier (URL or DID). */ - issuerId?: string; -} - -interface KeyManagerState { - keyPair: Ed25519KeyPairInstance; - issuerId: string; - verificationMethodId: string; -} - -let _state: KeyManagerState | null = null; - -/** - * Initialize the key manager: generate (or load) an Ed25519 key pair and - * register the controller document + verification method with the - * document loader. - * - * This should be called once at application startup. - */ -export async function initKeyManager( - options: KeyManagerOptions = {} -): Promise { - const issuerId = options.issuerId ?? DEFAULT_ISSUER_ID; - - // Generate a new key pair with the issuer as the controller - const keyPair: Ed25519KeyPairInstance = await Ed25519VerificationKey2020.generate({ - controller: issuerId, - }); - - // The key's id is auto-assigned as `${controller}#${keyFingerprint}` - const verificationMethodId = keyPair.id; - - // Register the controller document so the document loader can resolve it. - // This is what verifiers look up to find the public key. - // The controller document must use security/v2 context because it defines - // assertionMethod, authentication, and verificationMethod terms. - const controllerDoc = { - '@context': [ - 'https://w3id.org/security/v2', - 'https://w3id.org/security/suites/ed25519-2020/v1', - ], - id: issuerId, - assertionMethod: [verificationMethodId], - authentication: [verificationMethodId], - verificationMethod: [ - { - id: verificationMethodId, - type: 'Ed25519VerificationKey2020', - controller: issuerId, - publicKeyMultibase: keyPair.publicKeyMultibase, - }, - ], - }; - - registerDocument(issuerId, controllerDoc); - - // Also register the verification method URL directly so the signature - // verifier can dereference it by its full id. - registerDocument(verificationMethodId, { - '@context': 'https://w3id.org/security/suites/ed25519-2020/v1', - id: verificationMethodId, - type: 'Ed25519VerificationKey2020', - controller: issuerId, - publicKeyMultibase: keyPair.publicKeyMultibase, - }); - - _state = { keyPair, issuerId, verificationMethodId }; - return _state; -} - -/** Get the current key manager state. Throws if not initialized. */ -export function getKeyManager(): KeyManagerState { - if (!_state) { - throw new Error( - 'Key manager not initialized. Call initKeyManager() first.' - ); - } - return _state; -} - -/** Export the key pair (public key only by default). */ -export async function exportKeyPair( - options: { includePrivateKey?: boolean } = {} -): Promise { - const { keyPair } = getKeyManager(); - return keyPair.export({ - publicKey: true, - privateKey: options.includePrivateKey ?? false, - }); -} - -/** Reset the key manager (useful for testing). */ -export function resetKeyManager(): void { - _state = null; -} diff --git a/code/samples/typescript/src/common/vc/types.ts b/code/samples/typescript/src/common/vc/types.ts deleted file mode 100644 index 8c12ab57..00000000 --- a/code/samples/typescript/src/common/vc/types.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * TypeScript type declarations for the digitalbazaar VC ecosystem. - * - * These libraries are JavaScript-only; these types provide a typed surface - * for the parts of the API we use. - */ - -// -- Ed25519 Key Pair -- - -export interface Ed25519KeyPairExport { - id: string; - type: 'Ed25519VerificationKey2020'; - controller: string; - publicKeyMultibase: string; - privateKeyMultibase?: string; -} - -export interface Ed25519KeyPairInstance { - id: string; - type: string; - controller: string; - publicKeyMultibase: string; - privateKeyMultibase?: string; - signer(): { sign(params: { data: Uint8Array }): Promise }; - verifier(): { verify(params: { data: Uint8Array; signature: Uint8Array }): Promise }; - export(options: { publicKey?: boolean; privateKey?: boolean }): Promise; -} - -// -- Signature Suite -- - -export interface Ed25519Signature2020Instance { - verificationMethod: string; -} - -// -- Verifiable Credential -- - -export interface VerifiableCredential { - '@context': string[]; - id?: string; - type: string[]; - issuer: string | { id: string; [key: string]: unknown }; - issuanceDate: string; - expirationDate?: string; - credentialSubject: CredentialSubject | CredentialSubject[]; - proof?: Proof; - [key: string]: unknown; -} - -export interface CredentialSubject { - id?: string; - [key: string]: unknown; -} - -export interface Proof { - type: string; - created: string; - verificationMethod: string; - proofPurpose: string; - proofValue: string; - [key: string]: unknown; -} - -// -- Verifiable Presentation -- - -export interface VerifiablePresentation { - '@context': string[]; - type: string[]; - verifiableCredential?: VerifiableCredential[]; - holder?: string; - id?: string; - proof?: Proof; -} - -// -- Verification Results -- - -export interface VerifyCredentialResult { - verified: boolean; - results?: Array<{ verified: boolean; error?: Error }>; - error?: Error; -} - -export interface VerifyPresentationResult { - verified: boolean; - presentationResult?: { verified: boolean; error?: Error }; - credentialResults?: VerifyCredentialResult[]; - error?: Error; -} - -// -- Document Loader -- - -export interface DocumentLoaderResult { - contextUrl: string | null; - documentUrl: string; - document: unknown; -} - -export type DocumentLoader = (url: string) => Promise; diff --git a/code/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts b/code/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts index 4967b602..2bbb8fa1 100644 --- a/code/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts +++ b/code/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts @@ -20,14 +20,16 @@ * Each 'account' contains a user's payment methods and shipping address. * For demonstration purposes, several accounts are pre-populated with sample data. * - * Token creation now issues W3C Verifiable Credentials (VCs) via - * @digitalbazaar/vc with Ed25519Signature2020 signatures. + * Token creation issues SD-JWT payment credentials signed with the + * credentials-provider's ES256 key (AP2 v0.2). Sensitive payment method + * fields are made selectively disclosable. */ import { - issuePaymentCredential, - verifyAndExtractSubject, -} from '../../common/vc/index.js'; -import type { VerifiableCredential } from '../../common/vc/index.js'; + issueOpenMandate, + verifyMandate, + generateKeyPair, + type Es256KeyPair, +} from '../../common/sdjwt/index.js'; export type PaymentMethod = { type: string; @@ -161,9 +163,9 @@ const accountDb: { [email: string]: Account } = { }; /** - * Token store: maps a serialized VC (the "token" string) to its metadata. - * The VC itself is the token — it cryptographically binds the payment method - * to the issuer. The store tracks the mandate association. + * Token store: maps a serialized SD-JWT (the "token" string) to its metadata. + * The SD-JWT itself is the token — it cryptographically binds the payment + * method to the issuer. The store tracks the mandate association. */ const tokens: { [token: string]: { @@ -174,15 +176,61 @@ const tokens: { } = {}; /** - * Creates a token for an account by issuing a W3C Verifiable Credential. + * The credentials-provider's ES256 issuer keypair. Used to sign payment + * credential SD-JWTs and to verify them on the way back. The same keypair's + * public key is also bound into `cnf.jwk` as the holder key for these demo + * tokens (no separate holder key is involved at issuance time). + */ +let issuerKey: Es256KeyPair | null = null; + +/** + * Initializes the issuer ES256 keypair. Idempotent — repeated calls after the + * first are no-ops, so it is safe to invoke during server boot. + */ +export async function initIssuerKey(): Promise { + if (issuerKey) { + return; + } + issuerKey = await generateKeyPair(); +} + +/** + * Returns the initialized issuer keypair. + * + * @throws Error if {@link initIssuerKey} has not been called yet. + */ +function getIssuerKey(): Es256KeyPair { + if (!issuerKey) { + throw new Error('Issuer key not initialized. Call initIssuerKey() first.'); + } + return issuerKey; +} + +/** + * Payment method fields that carry sensitive data and should be made + * selectively disclosable in the issued SD-JWT. + */ +const DISCLOSABLE_FIELDS = [ + 'cryptogram', + 'token', + 'card_holder_name', + 'card_expiration', + 'card_billing_address', + 'account_number', + 'account_identifier', +]; + +/** + * Creates a token for an account by issuing an SD-JWT payment credential. * - * The VC embeds the payment method data as the credentialSubject and is - * signed with the credentials-provider's Ed25519 key. The serialized VC - * (JSON string) serves as the token. + * The SD-JWT embeds the payment method data as claims and is signed with the + * credentials-provider's ES256 issuer key. Sensitive payment method fields are + * made selectively disclosable. The serialized SD-JWT string serves as the + * token. * * @param emailAddress - The email address of the account. * @param paymentMethodAlias - The alias of the payment method. - * @returns The serialized VC token for the payment method. + * @returns The serialized SD-JWT token for the payment method. */ export const createToken = async ( emailAddress: string, @@ -195,14 +243,33 @@ export const createToken = async ( ); } - // Issue a W3C Verifiable Credential containing the payment method data. - const credential: VerifiableCredential = await issuePaymentCredential({ - subjectId: `mailto:${emailAddress}`, - paymentMethod: paymentMethod as unknown as Record, - }); + const key = getIssuerKey(); + + // Build the claim set from the payment method fields plus credential + // metadata. The credential `type` claim ("PaymentCredential") shadows the + // payment method's own `type` field (e.g. "CARD"), so the latter is + // preserved under `payment_method_type` and restored on verification. + const claims: Record = { + ...(paymentMethod as unknown as Record), + payment_method_type: paymentMethod.type, + sub: `mailto:${emailAddress}`, + payment_method_alias: paymentMethodAlias, + type: 'PaymentCredential', + iat: Math.floor(Date.now() / 1000), + }; + + // Only the sensitive fields actually present on this payment method are + // declared as selectively disclosable. + const disclosable = DISCLOSABLE_FIELDS.filter((field) => field in claims); - // Serialize the VC to use as the token string - const token = JSON.stringify(credential); + // Issue an SD-JWT payment credential signed by the issuer key. The issuer's + // public key is also bound as the holder key (cnf.jwk). + const token = await issueOpenMandate({ + claims, + disclosable, + issuerPrivateJwk: key.privateKey, + holderPublicJwk: key.publicKey, + }); tokens[token] = { emailAddress, @@ -231,15 +298,16 @@ export const updateToken = (token: string, paymentMandateId: string): void => { }; /** - * Verify a token (serialized VC) and return the payment method. + * Verify a token (serialized SD-JWT) and return the payment method. * - * Performs cryptographic verification of the VC signature, checks the - * mandate binding, and extracts the payment method from the credential subject. + * Performs cryptographic verification of the SD-JWT issuer signature, checks + * the in-memory mandate binding, and reconstructs the payment method from the + * verified claims. * - * @param token - The serialized VC token. + * @param token - The serialized SD-JWT token. * @param paymentMandateId - The payment mandate id associated with the token. - * @returns The payment method extracted from the verified VC. - * @throws Error if the token/VC is invalid or mandate doesn't match. + * @returns The payment method extracted from the verified SD-JWT. + * @throws Error if the token is invalid or the mandate doesn't match. */ export const verifyToken = async ( token: string, @@ -254,21 +322,32 @@ export const verifyToken = async ( throw new Error("Invalid token"); } - // Cryptographically verify the VC - let credential: VerifiableCredential; - try { - credential = JSON.parse(token) as VerifiableCredential; - } catch { - throw new Error("Invalid token: not a valid VC"); - } + const key = getIssuerKey(); - const subject = await verifyAndExtractSubject(credential); + // Cryptographically verify the SD-JWT issuer signature. + const { payload } = await verifyMandate({ + mandateSdJwt: token, + issuerPublicJwk: key.publicKey, + }); - // Reconstruct the PaymentMethod from the VC subject. - // Strip only the VC-specific `id` (subject DID/URI) and `paymentMandateId` - // metadata. The `type` field is kept because it holds the payment method - // type (e.g., "CARD"), not the JSON-LD type. - const { id: _id, paymentMandateId: _mandateId, ...paymentMethodData } = subject; + // Reconstruct the PaymentMethod from the verified claims. Strip the + // credential metadata (sub, payment_method_alias, type, iat) and the + // holder-binding `cnf` claim. The credential `type` ("PaymentCredential") + // is dropped; the payment method's own `type` (e.g. "CARD") was stashed in + // `payment_method_type` at issuance and is restored here. + const { + sub: _sub, + payment_method_alias: _alias, + type: _credentialType, + iat: _iat, + cnf: _cnf, + payment_method_type: paymentMethodType, + ...rest + } = payload; + const paymentMethodData: Record = { ...rest }; + if (paymentMethodType !== undefined) { + paymentMethodData.type = paymentMethodType; + } return paymentMethodData as unknown as PaymentMethod; }; diff --git a/code/samples/typescript/src/roles/credentials-provider-agent/server.ts b/code/samples/typescript/src/roles/credentials-provider-agent/server.ts index 517e00fe..b2ed8866 100644 --- a/code/samples/typescript/src/roles/credentials-provider-agent/server.ts +++ b/code/samples/typescript/src/roles/credentials-provider-agent/server.ts @@ -22,7 +22,7 @@ import { sessionService } from '../../common/config/session.js'; import { BaseAgentExecutor } from '../../common/server/base-executor.js'; import { bootstrapServer } from '../../common/server/bootstrap.js'; import { DATA_KEYS, A2A_DATA_KEYS } from '../../common/constants/index.js'; -import { initKeyManager } from '../../common/vc/index.js'; +import * as accountManager from './account-manager.js'; const runner = new Runner({ appName: 'ap2-credentials-provider', @@ -132,9 +132,9 @@ const agentCard: AgentCard = { version: '1.0.0', }; -// Initialize the VC key manager before starting the server so -// the credentials-provider can issue and verify Verifiable Credentials. -initKeyManager().then(() => { +// Initialize the issuer ES256 key before starting the server so the +// credentials-provider can issue and verify SD-JWT payment credentials. +accountManager.initIssuerKey().then(() => { bootstrapServer({ agentCard, agentExecutor, diff --git a/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts b/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts index dd471136..ac76467a 100644 --- a/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts +++ b/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts @@ -12,94 +12,157 @@ * Exposes three MCP tools consumed by the shopping agent v2 over stdio: * issue_payment_credential, revoke_payment_credential, verify_payment_receipt * - * NOTE: Minimum viable port. SD-JWT mandate-chain verification is STUBBED. + * issue_payment_credential performs REAL SD-JWT verification of the presented + * closed payment-mandate (KB-SD-JWT): it loads the mandate the agent persisted + * under TEMP_DB, verifies the issuer (user) signature and the agent Key-Binding + * JWT against `cnf.jwk` with the expected nonce, and only then mints a token — + * mirroring the Python credentials_provider_mcp.issue_payment_credential. * Tool names/args/response shapes mirror the v0.2 Python server. */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { randomUUID } from 'node:crypto'; +import fs from 'node:fs'; +import path from 'node:path'; import { z } from 'zod'; +import { verifyMandate, verifyJwtEs256, loadPublicJwk, type Es256KeyPair } from '../../common/sdjwt/index.js'; + +const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; const TOKEN_STORE = new Map(); +function readTempFile(filename: string): string | null { + try { + return fs.readFileSync(path.join(TEMP_DB, filename), 'utf-8'); + } catch { + return null; + } +} + +/** The user (issuer) public key persisted by the shopping agent's mandate-tools. */ +function loadUserPublicJwk(): JsonWebKey | null { + const raw = readTempFile('user_key.jwk.json'); + if (!raw) return null; + return (JSON.parse(raw) as Es256KeyPair).publicKey; +} + const server = new McpServer({ name: 'credentials-provider-mcp', version: '0.2.0', }); -server.tool( +server.registerTool( 'issue_payment_credential', { - payment_mandate_chain_id: z.string(), - open_checkout_hash: z.string(), - checkout_jwt_hash: z.string(), - payment_nonce: z.string(), + description: + 'Issue a scoped single-use payment token for a verified payment mandate chain, ' + + 'bound to the open-checkout and checkout-JWT hashes and a payment nonce.', + inputSchema: { + payment_mandate_chain_id: z.string(), + open_checkout_hash: z.string(), + checkout_jwt_hash: z.string(), + payment_nonce: z.string().optional(), + }, }, - async ({ payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash, payment_nonce }) => { - if (!payment_mandate_chain_id || !open_checkout_hash || !checkout_jwt_hash || !payment_nonce) { + async ({ payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash }) => { + if (!payment_mandate_chain_id || !open_checkout_hash || !checkout_jwt_hash) { + const error = { + error: 'missing_fields', + message: 'payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash are required', + }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ - error: 'missing_fields', - message: 'payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash, payment_nonce are required', - }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(error) }], + structuredContent: error, + isError: true, }; } - // STUB: real impl loads the sdjwt chain, verifies signatures and chain integrity, - // then issues a scoped single-use token. + // Load the presented closed payment-mandate (KB-SD-JWT) the agent persisted. + const presented = readTempFile(`${payment_mandate_chain_id}.sdjwt`); + if (!presented) { + const error = { error: 'mandate_not_found', message: payment_mandate_chain_id }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + const issuerPublicJwk = loadUserPublicJwk(); + if (!issuerPublicJwk) { + const error = { error: 'issuer_key_unavailable', message: 'user_key.jwk.json not found in TEMP_DB' }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + + // Verify the issuer (user) SD-JWT signature AND the agent Key-Binding JWT + // against cnf.jwk, requiring the holder binding to commit to the SPECIFIC + // checkout (nonce === checkout_jwt_hash). This is the hash-binding check: + // a payment presentation made for a different checkout is rejected here, + // and a forged/unbound presentation fails signature/KB verification. + try { + const { keyBound } = await verifyMandate({ + mandateSdJwt: presented, + issuerPublicJwk, + nonce: checkout_jwt_hash, + }); + if (!keyBound) { + const error = { error: 'checkout_binding_failed', message: 'payment mandate not holder-bound to this checkout_jwt_hash' }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + } catch (e) { + const error = { error: 'mandate_verification_failed', message: String(e) }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + + // Verified — mint a scoped single-use token bound to the mandate chain. const token = `pay_token_${randomUUID()}`; const issuedAt = Math.floor(Date.now() / 1000); const expiresAt = issuedAt + 600; TOKEN_STORE.set(token, { issued_at: issuedAt, expires_at: expiresAt }); + const result = { payment_token: token, expires_at: expiresAt }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ payment_token: token, expires_at: expiresAt }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(result) }], + structuredContent: result, }; }, ); -server.tool( +server.registerTool( 'revoke_payment_credential', - { payment_token: z.string() }, + { + description: + 'Revoke a previously issued payment token, removing it from the token store. ' + + 'Returns whether the token existed.', + inputSchema: { payment_token: z.string() }, + }, async ({ payment_token }) => { const existed = TOKEN_STORE.delete(payment_token); + const result = { revoked: existed, payment_token }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ revoked: existed, payment_token }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(result) }], + structuredContent: result, }; }, ); -server.tool( +server.registerTool( 'verify_payment_receipt', - { payment_receipt: z.string() }, + { + description: + 'Verify a payment receipt (stubbed: checks signature against the PSP key ' + + 'and that it matches a prior issued payment token).', + inputSchema: { payment_receipt: z.string() }, + }, async ({ payment_receipt }) => { - // STUB: real impl verifies the receipt's signature against the PSP key - // and that it matches a prior issued payment_token. - return { - content: [ - { - type: 'text', - text: JSON.stringify({ - verified: true, - receipt_prefix: payment_receipt.slice(0, 20), - }), - }, - ], - }; + // Verify the receipt's ES256 signature against the PSP public key. + const pspPub = loadPublicJwk(TEMP_DB, 'psp'); + if (!pspPub) { + const error = { error: 'psp_key_unavailable', message: 'psp key not found in TEMP_DB' }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + try { + const payload = await verifyJwtEs256(payment_receipt, pspPub); + const result = { verified: true, issuer: payload.iss, receipt_id: payload.receipt_id }; + return { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result }; + } catch (e) { + const error = { error: 'receipt_verification_failed', message: String(e) }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } }, ); diff --git a/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts b/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts index f4369e5c..e2c2b8f5 100644 --- a/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts +++ b/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts @@ -22,8 +22,23 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import fs from 'node:fs'; import path from 'node:path'; -import { randomUUID } from 'node:crypto'; +import { randomUUID, createHash } from 'node:crypto'; import { z } from 'zod'; +import { + signJwtEs256, + verifyJwtEs256, + loadOrCreateKeyPair, + loadPublicJwk, +} from '../../common/sdjwt/index.js'; + +/** Base64url SHA-256 — the checkout JWT hash binds the payment mandate. */ +function sha256Base64Url(input: string): string { + return createHash('sha256').update(input).digest('base64url'); +} + +/** PSP trigger endpoint used to settle and obtain a PSP-signed receipt. */ +const PSP_TRIGGER_PORT = process.env.MERCHANT_PAYMENT_PROCESSOR_TRIGGER_PORT ?? '8083'; +const PSP_INITIATE_URL = `http://localhost:${PSP_TRIGGER_PORT}/initiate-payment`; const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; const TRIGGER_STATE_PATH = @@ -32,6 +47,8 @@ const TRIGGER_STATE_PATH = const INVENTORY_PATH = process.env.MERCHANT_INVENTORY_PATH ?? path.join(TEMP_DB, 'merchant_inventory.json'); +const CARTS_PATH = + process.env.MERCHANT_CARTS_PATH ?? path.join(TEMP_DB, 'merchant_carts.json'); type InventoryEntry = { name: string; price: number; stock: number }; type TriggerEntry = { price?: number; stock?: number; _touch: number }; @@ -105,109 +122,133 @@ function resolveItem(itemId: string, priceCap: number | null = null): InventoryE return null; } -const CART_STORE = new Map(); +// Carts are persisted to TEMP_DB (not in-memory): the shopping agent v2 gives +// each sub-agent its own merchant MCP subprocess, so a cart assembled by one +// agent must be visible to another. File-backing keeps them consistent. +function loadCarts(): Record { + return loadJson(CARTS_PATH, {}); +} +function saveCart(cartId: string, cart: unknown): void { + const carts = loadCarts(); + carts[cartId] = cart; + saveJson(CARTS_PATH, carts); +} +function getCart(cartId: string): unknown { + return loadCarts()[cartId]; +} const server = new McpServer({ name: 'merchant-mcp', version: '0.2.0', }); -server.tool( +server.registerTool( 'search_inventory', { - product_description: z.string(), - constraint_price_cap: z.number().nullable().optional(), + description: + 'Search merchant inventory for a product matching a free-text description, ' + + 'optionally constrained by a price cap. Returns matching catalog entries.', + inputSchema: { + product_description: z.string(), + constraint_price_cap: z.number().nullable().optional(), + }, }, async ({ product_description, constraint_price_cap }) => { if (!product_description.trim()) { + const error = { + error: 'invalid_description', + message: 'product_description must be non-empty', + }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ - error: 'invalid_description', - message: 'product_description must be non-empty', - }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(error) }], + structuredContent: error, + isError: true, }; } const entry = generateInventoryEntry( product_description, constraint_price_cap ?? null, ); + const result = { + matches: [entry], + message: + `Found 1 matching product: ${entry.item_id}. ` + + 'Stock is 0 until a drop is simulated via the trigger server.', + }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ - matches: [entry], - message: - `Found 1 matching product: ${entry.item_id}. ` + - 'Stock is 0 until a drop is simulated via the trigger server.', - }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(result) }], + structuredContent: result, }; }, ); -server.tool( +server.registerTool( 'check_product', { - item_id: z.string(), - constraint_price_cap: z.number().nullable().optional(), + description: + 'Check the current price and availability of a specific catalog item by id, ' + + 'applying any active price/stock trigger overrides.', + inputSchema: { + item_id: z.string(), + constraint_price_cap: z.number().nullable().optional(), + }, }, async ({ item_id, constraint_price_cap }) => { const item = resolveItem(item_id, constraint_price_cap ?? null); if (!item) { + const error = { error: 'item_not_found' }; return { - content: [{ type: 'text', text: JSON.stringify({ error: 'item_not_found' }) }], + content: [{ type: 'text', text: JSON.stringify(error) }], + structuredContent: error, + isError: true, }; } const price = effectivePrice(item_id, item.price); const { stock } = triggerOverrides(item_id); const available = stock !== undefined && stock > 0; + const result = { + item_id, + price, + available, + timestamp: Math.floor(Date.now() / 1000), + payment_method: process.env.FLOW ?? 'card', + payment_method_description: 'Card (stub)', + }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ - item_id, - price, - available, - timestamp: Math.floor(Date.now() / 1000), - payment_method: process.env.FLOW ?? 'card', - payment_method_description: 'Card (stub)', - }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(result) }], + structuredContent: result, }; }, ); -server.tool( +server.registerTool( 'assemble_cart', - { item_id: z.string(), qty: z.number().int().positive() }, + { + description: + 'Assemble a cart for a given in-stock item and quantity, computing line ' + + 'items and totals in minor units, and persisting it for checkout.', + inputSchema: { item_id: z.string(), qty: z.number().int().positive() }, + }, async ({ item_id, qty }) => { const item = resolveItem(item_id); if (!item) { + const error = { error: 'item_not_found' }; return { - content: [{ type: 'text', text: JSON.stringify({ error: 'item_not_found' }) }], + content: [{ type: 'text', text: JSON.stringify(error) }], + structuredContent: error, + isError: true, }; } const { stock } = triggerOverrides(item_id); if (!(stock !== undefined && stock > 0)) { + const error = { + error: 'out_of_stock', + message: 'Item is not available to purchase yet (e.g. drop not live).', + }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ - error: 'out_of_stock', - message: 'Item is not available to purchase yet (e.g. drop not live).', - }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(error) }], + structuredContent: error, + isError: true, }; } const price = effectivePrice(item_id, item.price); @@ -222,66 +263,142 @@ server.tool( ], currency: 'USD', }; - CART_STORE.set(cartId, cart); - return { content: [{ type: 'text', text: JSON.stringify(cart) }] }; + saveCart(cartId, cart); + return { + content: [{ type: 'text', text: JSON.stringify(cart) }], + structuredContent: cart, + }; }, ); -server.tool( +server.registerTool( 'create_checkout', { - cart_id: z.string(), - open_checkout_mandate_id: z.string(), + description: + 'Create a checkout for a previously assembled cart, binding it to an ' + + 'open checkout mandate and returning a (stubbed) signed checkout JWT and its hash.', + inputSchema: { + cart_id: z.string(), + open_checkout_mandate_id: z.string(), + }, }, async ({ cart_id, open_checkout_mandate_id }) => { - const cart = CART_STORE.get(cart_id); + const cart = getCart(cart_id); if (!cart) { + const error = { error: 'cart_not_found' }; return { - content: [{ type: 'text', text: JSON.stringify({ error: 'cart_not_found' }) }], + content: [{ type: 'text', text: JSON.stringify(error) }], + structuredContent: error, + isError: true, }; } - // STUB: real impl creates ES256-signed JWT. Here we just return a placeholder. - const checkoutJwt = `stub.${Buffer.from(JSON.stringify(cart)).toString('base64url')}.sig`; - const checkoutJwtHash = Buffer.from(checkoutJwt).toString('base64url').slice(0, 43); + // Hash the referenced open checkout mandate so the checkout JWT commits to + // it (downstream binding). The agent persisted it under TEMP_DB. + let openCheckoutHash: string | null = null; + try { + const openMandate = fs.readFileSync(path.join(TEMP_DB, `${open_checkout_mandate_id}.sdjwt`), 'utf-8'); + openCheckoutHash = sha256Base64Url(openMandate); + } catch { + const error = { error: 'open_mandate_not_found', message: open_checkout_mandate_id }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + // Sign a real ES256 checkout JWT with the merchant key. A random `jti` + // gives the JWT entropy (the AP2 spec warns deterministic signatures over + // low-entropy checkout content are rainbow-table-able). The JWT commits to + // the open checkout mandate via open_checkout_hash. + const merchant = await loadOrCreateKeyPair(TEMP_DB, 'merchant'); + const checkoutJwt = await signJwtEs256( + { + iss: 'merchant-agent', + iat: Math.floor(Date.now() / 1000), + jti: randomUUID(), + open_checkout_mandate_id, + open_checkout_hash: openCheckoutHash, + cart, + }, + merchant.privateKey, + ); + const checkoutJwtHash = sha256Base64Url(checkoutJwt); + const result = { + checkout_jwt: checkoutJwt, + checkout_jwt_hash: checkoutJwtHash, + open_checkout_hash: openCheckoutHash, + open_checkout_mandate_id, + cart, + }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ - checkout_jwt: checkoutJwt, - checkout_jwt_hash: checkoutJwtHash, - open_checkout_mandate_id, - cart, - }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(result) }], + structuredContent: result, }; }, ); -server.tool( +server.registerTool( 'complete_checkout', { - checkout_jwt: z.string(), - payment_credential: z.unknown().optional(), + description: + 'Complete a checkout using a checkout JWT and optional payment credential, ' + + 'returning a (stubbed) signed checkout receipt.', + inputSchema: { + checkout_jwt: z.string(), + payment_credential: z.unknown().optional(), + }, }, - async ({ checkout_jwt }) => { - // STUB: real impl calls PSP initiate_payment, validates, returns signed receipt. + async ({ checkout_jwt, payment_credential }) => { + // Verify the merchant's own checkout JWT signature before finalizing. + const merchantPub = loadPublicJwk(TEMP_DB, 'merchant'); + if (!merchantPub) { + const error = { error: 'merchant_key_unavailable', message: 'merchant key not found in TEMP_DB' }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + let checkoutJwtHash: string; + try { + await verifyJwtEs256(checkout_jwt, merchantPub); + checkoutJwtHash = sha256Base64Url(checkout_jwt); + } catch (e) { + const error = { error: 'invalid_checkout_jwt', message: String(e) }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + + // Settle with the PSP (server-to-server) and obtain a PSP-signed payment + // receipt — mirrors Python merchant_agent_mcp._initiate_payment_with_payment_processor. + let paymentReceipt: string | null = null; + try { + const res = await fetch(PSP_INITIATE_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + payment_token: (payment_credential as { payment_token?: string })?.payment_token ?? 'tok_unknown', + checkout_jwt_hash: checkoutJwtHash, + open_checkout_hash: checkoutJwtHash, + }), + }); + const body = (await res.json()) as { payment_receipt?: string }; + paymentReceipt = body.payment_receipt ?? null; + } catch (e) { + const error = { error: 'psp_settlement_failed', message: String(e) }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + + // Sign the merchant's checkout receipt (ES256) over the verified checkout. + const merchant = await loadOrCreateKeyPair(TEMP_DB, 'merchant'); + const checkoutReceipt = await signJwtEs256( + { + iss: 'merchant-agent', + receipt_id: randomUUID(), + iat: Math.floor(Date.now() / 1000), + checkout_jwt_hash: checkoutJwtHash, + }, + merchant.privateKey, + ); + const result = { + status: 'completed', + checkout_receipt: checkoutReceipt, // merchant-signed ES256 JWT + payment_receipt: paymentReceipt, // PSP-signed ES256 JWT + }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ - status: 'completed', - checkout_receipt: { - receipt_id: randomUUID(), - timestamp: Math.floor(Date.now() / 1000), - checkout_jwt_hash: checkout_jwt.slice(0, 43), - signature: 'stub-signature', - }, - }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(result) }], + structuredContent: result, }; }, ); diff --git a/code/samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts b/code/samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts index c87fccc4..11a14da0 100644 --- a/code/samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts +++ b/code/samples/typescript/src/roles/merchant-payment-processor-mcp/server.ts @@ -12,60 +12,75 @@ * Exposes one MCP tool consumed by the shopping agent v2 over stdio: * initiate_payment * - * NOTE: Minimum viable port. Mandate verification, token-store lookup, and - * receipt signing are STUBBED. Tool contract mirrors the v0.2 Python server. + * Settles the payment and signs a real ES256 payment receipt with the PSP key + * (the same receipt the PSP trigger server produces). Token-store lookup is + * still simplified. Tool contract mirrors the v0.2 Python server. */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { randomUUID } from 'node:crypto'; import { z } from 'zod'; +import { signJwtEs256, loadOrCreateKeyPair } from '../../common/sdjwt/index.js'; + +const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; const server = new McpServer({ name: 'merchant-payment-processor-mcp', version: '0.2.0', }); -server.tool( +server.registerTool( 'initiate_payment', { - payment_token: z.string(), - checkout_jwt_hash: z.string(), - open_checkout_hash: z.string(), + description: + 'Initiate and settle a payment for a previously issued payment token, bound to ' + + 'the checkout-JWT and open-checkout hashes, returning a (stubbed) signed PSP receipt.', + inputSchema: { + payment_token: z.string(), + checkout_jwt_hash: z.string(), + open_checkout_hash: z.string(), + }, }, async ({ payment_token, checkout_jwt_hash, open_checkout_hash }) => { if (!payment_token || !checkout_jwt_hash || !open_checkout_hash) { + const error = { + error: 'missing_fields', + message: + 'payment_token, checkout_jwt_hash, and open_checkout_hash are required', + }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ - error: 'missing_fields', - message: - 'payment_token, checkout_jwt_hash, and open_checkout_hash are required', - }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(error) }], + structuredContent: error, + isError: true, }; } - // STUB: real impl looks up token, verifies mandate chain, settles payment, - // signs receipt with PSP key, and POSTs to credentials-provider trigger. - const receipt = `psp_receipt.${randomUUID()}.stub_sig`; + // Settle and sign a real ES256 payment receipt with the PSP key. + const psp = await loadOrCreateKeyPair(TEMP_DB, 'psp'); + const receipt = await signJwtEs256( + { + iss: 'merchant-payment-processor', + receipt_id: randomUUID(), + iat: Math.floor(Date.now() / 1000), + checkout_jwt_hash, + open_checkout_hash, + payment_token, + status: 'settled', + }, + psp.privateKey, + ); + const result = { + status: 'settled', + payment_receipt: receipt, + checkout_jwt_hash, + open_checkout_hash, + amount: 1500, + currency: 'USD', + timestamp: Math.floor(Date.now() / 1000), + }; return { - content: [ - { - type: 'text', - text: JSON.stringify({ - status: 'settled', - payment_receipt: receipt, - checkout_jwt_hash, - open_checkout_hash, - amount: 1500, - currency: 'USD', - timestamp: Math.floor(Date.now() / 1000), - }), - }, - ], + content: [{ type: 'text', text: JSON.stringify(result) }], + structuredContent: result, }; }, ); diff --git a/code/samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts b/code/samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts index 94478653..5f5887a1 100644 --- a/code/samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts +++ b/code/samples/typescript/src/roles/merchant-payment-processor-mcp/trigger-server.ts @@ -13,13 +13,15 @@ import express, { type Request, type Response } from 'express'; import { randomUUID } from 'node:crypto'; +import { signJwtEs256, loadOrCreateKeyPair } from '../../common/sdjwt/index.js'; const PORT = Number(process.env.MERCHANT_PAYMENT_PROCESSOR_TRIGGER_PORT ?? 8083); +const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; const app = express(); app.use(express.json({ limit: '1mb' })); -app.post('/initiate-payment', (req: Request, res: Response) => { +app.post('/initiate-payment', async (req: Request, res: Response) => { const { payment_token, checkout_jwt_hash, open_checkout_hash } = req.body ?? {}; for (const [field, value] of [ ['payment_token', payment_token], @@ -28,12 +30,22 @@ app.post('/initiate-payment', (req: Request, res: Response) => { ] as const) { if (!value) return res.status(400).json({ error: `${field} required` }); } - // STUB: real impl calls into MCP server's initiate_payment for token verification + settlement. - res.json({ - status: 'settled', - payment_receipt: `psp_receipt.${randomUUID()}.stub_sig`, - timestamp: Math.floor(Date.now() / 1000), - }); + // Settle and sign a real ES256 payment receipt with the PSP key. The + // credentials-provider verifies this against the PSP public key. + const psp = await loadOrCreateKeyPair(TEMP_DB, 'psp'); + const paymentReceipt = await signJwtEs256( + { + iss: 'merchant-payment-processor', + receipt_id: randomUUID(), + iat: Math.floor(Date.now() / 1000), + checkout_jwt_hash, + open_checkout_hash, + payment_token, + status: 'settled', + }, + psp.privateKey, + ); + res.json({ status: 'settled', payment_receipt: paymentReceipt, timestamp: Math.floor(Date.now() / 1000) }); }); app.listen(PORT, '127.0.0.1', () => { diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/agent.ts b/code/samples/typescript/src/roles/shopping-agent-v2/agent.ts index c9f7da73..2269cae8 100644 --- a/code/samples/typescript/src/roles/shopping-agent-v2/agent.ts +++ b/code/samples/typescript/src/roles/shopping-agent-v2/agent.ts @@ -9,18 +9,27 @@ * * Shopping Agent v2 — Human-Not-Present. * - * Single LlmAgent (MVP — Python has a consent/monitoring/purchase hierarchy) - * that owns three MCP toolsets (merchant, credentials-provider, payment-processor) - * plus the mandate helper tools. Each MCP toolset is a long-lived stdio Client - * connected to the corresponding `*-mcp/server.ts` subprocess. + * Mirrors the Python `shopping_agent_v2` sub-agent hierarchy: + * consent_agent (root) -> monitoring_agent -> purchase_agent (leaf). + * + * - consent_agent: drop/budget dialogue + open-mandate signing, then transfers + * to monitoring. + * - monitoring_agent: watches price/availability, transfers to purchase when the + * open-mandate constraints are satisfied and the item is available. + * - purchase_agent: runs the autonomous purchase pipeline (assemble cart -> + * create checkout -> closed-mandate presentations -> issue payment credential + * -> complete checkout -> verify receipts). + * + * Each agent owns its OWN set of MCPToolset instances (separate stdio + * connections) per ADK best practice — sharing a single toolset across agents + * causes stdio connection conflicts (see google/adk-python#712). Each MCP + * toolset launches the corresponding `*-mcp/server.ts` as a stdio subprocess. */ -import { FunctionTool, LlmAgent } from '@google/adk'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { LlmAgent, MCPToolset } from '@google/adk'; +import type { BaseTool } from '@google/adk'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { z } from 'zod'; import { assembleAndSignMandatesTool, @@ -28,167 +37,144 @@ import { createCheckoutPresentationTool, createPaymentPresentationTool, verifyCheckoutReceiptTool, + resetTempDbTool, } from './mandate-tools.js'; +import { CONSENT_INSTRUCTION } from './prompts/consent.js'; +import { MONITORING_INSTRUCTION } from './prompts/monitoring.js'; +import { PURCHASE_INSTRUCTION } from './prompts/purchase.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROLES_DIR = path.resolve(__dirname, '..'); -async function makeMcpClient(serverEntry: string): Promise { - const transport = new StdioClientTransport({ - command: 'npx', - args: ['tsx', serverEntry], - env: { - ...process.env, - LOGS_DIR: process.env.LOGS_DIR ?? path.resolve(ROLES_DIR, '../../.logs'), - TEMP_DB_DIR: process.env.TEMP_DB_DIR ?? path.resolve(ROLES_DIR, '../../.temp-db'), - } as Record, - }); - const client = new Client({ name: 'shopping-agent-v2', version: '0.2.0' }); - await client.connect(transport); - return client; -} +const MERCHANT_SERVER = path.join(ROLES_DIR, 'merchant-agent-mcp/server.ts'); +const CREDENTIAL_SERVER = path.join(ROLES_DIR, 'credentials-provider-mcp/server.ts'); +const PSP_SERVER = path.join(ROLES_DIR, 'merchant-payment-processor-mcp/server.ts'); + +const MCP_ENV = { + ...process.env, + LOGS_DIR: process.env.LOGS_DIR ?? path.resolve(ROLES_DIR, '../../.logs'), + TEMP_DB_DIR: process.env.TEMP_DB_DIR ?? path.resolve(ROLES_DIR, '../../.temp-db'), +} as Record; -function mcpTool( - client: Client, - toolName: string, - description: string, - parameters: z.ZodObject, -): FunctionTool { - return new FunctionTool({ - name: toolName, - description, - parameters, - execute: async (args: unknown) => { - const result = (await client.callTool({ - name: toolName, - arguments: args as Record, - })) as { content?: Array<{ type: string; text?: string }> }; - const first = result.content?.[0]; - if (first?.type === 'text' && first.text) { - try { return JSON.parse(first.text); } catch { return first.text; } - } - return result; +/** + * Build a fresh MCPToolset that launches an `*-mcp/server.ts` over stdio. + * + * A new instance is created per call (and per agent) on purpose: sharing one + * toolset across agents reuses the same stdio session and conflicts. + * + * @param serverEntry Absolute path to the MCP server entrypoint. + * @param toolFilter Optional allowlist of tool names or a predicate. + * @returns A configured MCPToolset. + */ +function makeMcpToolset( + serverEntry: string, + toolFilter?: string[] | ((tool: BaseTool) => boolean), +): MCPToolset { + return new MCPToolset( + { + type: 'StdioConnectionParams', + serverParams: { + command: 'npx', + args: ['tsx', serverEntry], + env: MCP_ENV, + }, + timeout: 60_000, }, - }); + toolFilter, + ); } -const merchantClient = await makeMcpClient(path.join(ROLES_DIR, 'merchant-agent-mcp/server.ts')); -const credentialsClient = await makeMcpClient(path.join(ROLES_DIR, 'credentials-provider-mcp/server.ts')); -const pspClient = await makeMcpClient(path.join(ROLES_DIR, 'merchant-payment-processor-mcp/server.ts')); - -const merchantTools = [ - mcpTool( - merchantClient, - 'search_inventory', - 'Search the merchant inventory by natural-language description. Returns at most one matching product. Stock is 0 until a price-drop trigger fires.', - z.object({ - product_description: z.string(), - constraint_price_cap: z.number().nullable().optional(), - }), - ), - mcpTool( - merchantClient, - 'check_product', - 'Return the current price and availability of a known item_id (e.g. supershoe_size_9_0). Stock becomes >0 only after the trigger fires.', - z.object({ - item_id: z.string(), - constraint_price_cap: z.number().nullable().optional(), - }), - ), - mcpTool( - merchantClient, - 'assemble_cart', - 'Build a cart for the given item_id and qty after check_product reports available=true.', - z.object({ item_id: z.string(), qty: z.number().int().positive() }), - ), - mcpTool( - merchantClient, - 'create_checkout', - 'Issue an ES256-signed checkout JWT for the cart and the open checkout mandate.', - z.object({ cart_id: z.string(), open_checkout_mandate_id: z.string() }), - ), - mcpTool( - merchantClient, - 'complete_checkout', - 'Hand the issued payment credential back to the merchant to finalize the order and emit a checkout receipt.', - z.object({ checkout_jwt: z.string(), payment_credential: z.unknown().optional() }), - ), -]; +const MODEL = process.env.AGENT_MODEL ?? 'gemini-2.5-flash'; -const credentialsTools = [ - mcpTool( - credentialsClient, - 'issue_payment_credential', - 'Verify the closed payment mandate chain and issue a scoped, single-use payment token.', - z.object({ - payment_mandate_chain_id: z.string(), - open_checkout_hash: z.string(), - checkout_jwt_hash: z.string(), - payment_nonce: z.string(), - }), - ), - mcpTool( - credentialsClient, - 'revoke_payment_credential', - 'Revoke a previously issued payment token.', - z.object({ payment_token: z.string() }), - ), - mcpTool( - credentialsClient, - 'verify_payment_receipt', - 'Verify a PSP-signed payment receipt against the issued payment token.', - z.object({ payment_receipt: z.string() }), - ), -]; - -const pspTools = [ - mcpTool( - pspClient, - 'initiate_payment', - 'Submit a settlement request to the PSP using the issued payment token. Returns a signed payment receipt.', - z.object({ - payment_token: z.string(), - checkout_jwt_hash: z.string(), - open_checkout_hash: z.string(), - }), - ), -]; +/** + * Reformat any MCP/mandate tool error into a structured artifact and escalate + * so the LLM reliably stops and emits exactly {type:'error', error, message}. + * + * Returning a replacement object becomes the tool result the model sees, and + * setting `context.actions.escalate` halts further sub-agent processing. + */ +const errorEscalationCallback = ({ + tool, + response, + context, +}: { + tool: BaseTool; + args: Record; + response: Record; + context: { actions: { escalate?: boolean } }; +}): Record | undefined => { + if (response && typeof response === 'object' && 'error' in response) { + const error = (response as { error: unknown }).error; + const message = 'message' in response ? (response as { message: unknown }).message : String(response); + context.actions.escalate = true; + const errorJson = JSON.stringify({ type: 'error', error, message }); + return { + error, + message, + action_required: + `STOP all processing for tool "${tool.name}". Emit EXACTLY this JSON ` + + `as your complete response, nothing else: ${errorJson}`, + }; + } + return undefined; +}; -export const shoppingAgentV2 = new LlmAgent({ - name: 'root_agent', - model: process.env.AGENT_MODEL ?? 'gemini-2.5-flash', +export const purchaseAgent = new LlmAgent({ + name: 'purchase_agent', + model: MODEL, description: - 'Human-Not-Present shopping agent. Captures user intent, signs an open mandate, ' + - 'monitors merchant for price/availability, and autonomously executes the purchase ' + - 'when the constraint is satisfied.', - instruction: `You are a Human-Not-Present shopping agent. - -Flow: -1. Capture the user's intent (item, price cap, expiry). Call search_inventory once to register the item and get its item_id. -2. Call assembleAndSignMandates with the natural_language_description, constraint_price_cap, and an expires_at_iso string (1 hour from now). This produces open_checkout_mandate_id, open_payment_mandate_id, and their hashes. -3. To check current state, call check_product with the item_id from step 1. -4. Call checkConstraintsAgainstMandate with the open_checkout_mandate_id, current_price, and available. If satisfies = false, tell the user you'll keep watching and stop. -5. When satisfies = true, execute the autonomous purchase: - a. assemble_cart(item_id, qty=1) - b. create_checkout(cart_id, open_checkout_mandate_id) - c. createCheckoutPresentation, createPaymentPresentation - d. issue_payment_credential(payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash, payment_nonce — generate a random nonce) - e. initiate_payment(payment_token, checkout_jwt_hash, open_checkout_hash) - f. complete_checkout(checkout_jwt, payment_credential) - g. verify_payment_receipt, verifyCheckoutReceipt -6. Surface a brief receipt summary to the user. - -If a tool returns an object with an "error" key, STOP and return the error verbatim.`, + 'Executes the autonomous purchase flow once price and availability satisfy ' + + 'the open mandates: assemble_cart, create_checkout, closed mandates, ' + + 'issue_payment_credential, complete_checkout, and receipt verification.', + instruction: PURCHASE_INSTRUCTION, + outputKey: 'purchase_result', tools: [ - assembleAndSignMandatesTool, checkConstraintsAgainstMandateTool, createCheckoutPresentationTool, createPaymentPresentationTool, verifyCheckoutReceiptTool, - ...merchantTools, - ...credentialsTools, - ...pspTools, + makeMcpToolset(MERCHANT_SERVER), + makeMcpToolset(CREDENTIAL_SERVER), + // The purchase agent must NOT settle directly via the PSP; filter out + // initiate_payment (mirrors the Python tool_filter lambda). + makeMcpToolset(PSP_SERVER, (tool) => tool.name !== 'initiate_payment'), ], + afterToolCallback: errorEscalationCallback, }); +export const monitoringAgent = new LlmAgent({ + name: 'monitoring_agent', + model: MODEL, + description: + 'Holds the open mandates and monitors item price and availability via ' + + 'check_product. Transfers to purchase_agent when the price is within the ' + + 'mandate and the merchant reports the item as available.', + instruction: MONITORING_INSTRUCTION, + outputKey: 'monitoring_result', + tools: [checkConstraintsAgainstMandateTool, makeMcpToolset(MERCHANT_SERVER)], + subAgents: [purchaseAgent], + afterToolCallback: errorEscalationCallback, +}); + +export const consentAgent = new LlmAgent({ + name: 'consent_agent', + model: MODEL, + description: + 'Handles drop/budget dialogue, item selection, and open-mandate signing. ' + + 'Transfers to monitoring_agent after the mandates are approved or on ' + + '"Check price now".', + instruction: CONSENT_INSTRUCTION, + outputKey: 'consent_result', + tools: [ + resetTempDbTool, + assembleAndSignMandatesTool, + checkConstraintsAgainstMandateTool, + makeMcpToolset(MERCHANT_SERVER), + ], + subAgents: [monitoringAgent], + afterToolCallback: errorEscalationCallback, +}); + +export const shoppingAgentV2 = consentAgent; + export { shoppingAgentV2 as rootAgent }; diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts b/code/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts index b69575c7..90dfb0dc 100644 --- a/code/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts +++ b/code/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts @@ -7,8 +7,20 @@ * * https://www.apache.org/licenses/LICENSE-2.0 * - * Mandate helper tools used by shopping-agent-v2. STUBS — see header in - * adjacent files. Names and arg shapes mirror Python `shopping_agent.mandate_tools`. + * Mandate helper tools used by shopping-agent-v2, backed by real SD-JWT + * (src/common/sdjwt, @sd-jwt/core + ES256): + * - assembleAndSignMandates: issues open-checkout + open-payment SD-JWTs with + * a cnf.jwk agent key (RFC 7800). Constraints (price cap, merchant + * allowlist) are carried as claims so they can be verified later. + * - checkConstraintsAgainstMandate: verifies the open mandate and checks the + * observed price/merchant against its constraints (real, not a stub). + * - createCheckout/PaymentPresentation: KB-SD-JWT presentations whose holder + * binding commits to the checkout_jwt_hash (so a presentation cannot be + * replayed against a different checkout). + * - verifyCheckoutReceipt: verifies a presented mandate's signature + KB. + * - resetTempDb: clears mandate files for a fresh run, preserving keys. + * + * Names mirror Python `shopping_agent.mandate_tools`. */ import { FunctionTool } from '@google/adk'; @@ -17,39 +29,91 @@ import path from 'node:path'; import { randomUUID, createHash } from 'node:crypto'; import { z } from 'zod'; +import { + issueOpenMandate, + presentClosedMandate, + verifyMandate, + loadOrCreateKeyPair, + checkConstraints, +} from '../../common/sdjwt/index.js'; + const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; +const CREDENTIAL_PROVIDER_AUD = 'credentials-provider'; -function sha256Base64Url(input: string): string { - return createHash('sha256').update(input).digest('base64url'); +/** Mandate file prefixes — cleared by resetTempDb, never the *_key.jwk.json keys. */ +const MANDATE_PREFIXES = ['open_chk_', 'open_pay_', 'chk_', 'pay_']; + +function tempPath(filename: string): string { + return path.join(TEMP_DB, filename); } -function persistMandate(filename: string, content: string): void { +function persist(filename: string, content: string): void { fs.mkdirSync(TEMP_DB, { recursive: true }); - fs.writeFileSync(path.join(TEMP_DB, filename), content); + fs.writeFileSync(tempPath(filename), content); +} + +function readIfExists(filename: string): string | null { + try { + return fs.readFileSync(tempPath(filename), 'utf-8'); + } catch { + return null; + } +} + +function sha256Base64Url(input: string): string { + return createHash('sha256').update(input).digest('base64url'); } export const assembleAndSignMandatesTool = new FunctionTool({ name: 'assembleAndSignMandates', description: - 'STUB: Builds and signs the open-checkout and open-payment mandate SD-JWT chains based on the user\'s intent and constraints. Persists them under TEMP_DB and returns their IDs and hashes.', + 'Builds and signs the open-checkout and open-payment mandates as real SD-JWTs (ES256) with a cnf.jwk agent key for key binding. The price cap and optional merchant allowlist are carried as mandate constraints. Persists them under TEMP_DB and returns their IDs and sd-hashes.', parameters: z.object({ natural_language_description: z.string(), constraint_price_cap: z.number(), expires_at_iso: z.string(), + allowed_merchants: z.array(z.string()).optional(), user_cart_confirmation_required: z.boolean().optional(), }), execute: async (args) => { + const user = await loadOrCreateKeyPair(TEMP_DB, 'user'); + const agent = await loadOrCreateKeyPair(TEMP_DB, 'agent'); + + // Constraints are always-present claims (not selectively disclosed) so the + // monitoring agent can read them back when checking the open mandate. + const baseClaims = { + natural_language_description: args.natural_language_description, + constraint_price_cap: args.constraint_price_cap, + allowed_merchants: args.allowed_merchants ?? [], + currency: 'USD', + expires_at_iso: args.expires_at_iso, + user_cart_confirmation_required: args.user_cart_confirmation_required ?? true, + }; + const openCheckoutId = `open_chk_${randomUUID()}`; const openPaymentId = `open_pay_${randomUUID()}`; - const checkoutBody = JSON.stringify({ ...args, kind: 'open_checkout', id: openCheckoutId }); - const paymentBody = JSON.stringify({ ...args, kind: 'open_payment', id: openPaymentId }); - persistMandate(`${openCheckoutId}.sdjwt`, `stub.${Buffer.from(checkoutBody).toString('base64url')}.sig`); - persistMandate(`${openPaymentId}.sdjwt`, `stub.${Buffer.from(paymentBody).toString('base64url')}.sig`); + + const openCheckout = await issueOpenMandate({ + claims: { ...baseClaims, kind: 'open_checkout' }, + disclosable: ['natural_language_description'], + issuerPrivateJwk: user.privateKey, + holderPublicJwk: agent.publicKey, + }); + const openPayment = await issueOpenMandate({ + claims: { ...baseClaims, kind: 'open_payment' }, + disclosable: ['natural_language_description'], + issuerPrivateJwk: user.privateKey, + holderPublicJwk: agent.publicKey, + }); + + persist(`${openCheckoutId}.sdjwt`, openCheckout); + persist(`${openPaymentId}.sdjwt`, openPayment); + return { open_checkout_mandate_id: openCheckoutId, - open_checkout_hash: sha256Base64Url(checkoutBody), + open_checkout_hash: sha256Base64Url(openCheckout), open_payment_mandate_id: openPaymentId, - open_payment_hash: sha256Base64Url(paymentBody), + open_payment_hash: sha256Base64Url(openPayment), }; }, }); @@ -57,54 +121,145 @@ export const assembleAndSignMandatesTool = new FunctionTool({ export const checkConstraintsAgainstMandateTool = new FunctionTool({ name: 'checkConstraintsAgainstMandate', description: - 'Check whether the current price + availability of an item satisfies the constraints in the open mandate.', + "Verifies the open checkout mandate and checks the observed price (and optional merchant) against the mandate's constraints (price cap, merchant allowlist). Returns meets_constraints + any violations.", parameters: z.object({ open_checkout_mandate_id: z.string(), current_price: z.number(), available: z.boolean(), + merchant: z.string().optional(), + currency: z.string().optional(), }), - execute: async ({ available, current_price }) => { - if (!available) return { satisfies: false, reason: 'item_not_available' }; - if (current_price <= 0) return { satisfies: false, reason: 'invalid_price' }; - return { satisfies: true }; + execute: async ({ open_checkout_mandate_id, current_price, available, merchant, currency }) => { + if (!available) { + return { meets_constraints: false, satisfies: false, violations: ['item_not_available'] }; + } + const open = readIfExists(`${open_checkout_mandate_id}.sdjwt`); + if (!open) { + return { error: 'open_mandate_not_found', message: open_checkout_mandate_id }; + } + const user = await loadOrCreateKeyPair(TEMP_DB, 'user'); + let claims: Record; + try { + claims = (await verifyMandate({ mandateSdJwt: open, issuerPublicJwk: user.publicKey })).payload; + } catch (e) { + return { error: 'mandate_verification_failed', message: String(e) }; + } + + const priceCap = typeof claims.constraint_price_cap === 'number' ? claims.constraint_price_cap : undefined; + const allowedMerchants = Array.isArray(claims.allowed_merchants) + ? (claims.allowed_merchants as string[]) + : undefined; + + const { meetsConstraints, violations } = checkConstraints( + { price: current_price, currency, merchant }, + { priceCap, currency: typeof claims.currency === 'string' ? claims.currency : undefined, allowedMerchants }, + ); + + return { + meets_constraints: meetsConstraints, + satisfies: meetsConstraints, // alias for older prompt wording + violations, + price_cap: priceCap, + price: current_price, + available, + }; }, }); export const createCheckoutPresentationTool = new FunctionTool({ name: 'createCheckoutPresentation', - description: 'STUB: Build the closed-checkout mandate SD-JWT presentation from the open mandate and a concrete cart.', + description: + 'Builds the closed-checkout mandate as a KB-SD-JWT presentation whose holder binding commits to checkout_jwt_hash, from the persisted open-checkout mandate.', parameters: z.object({ open_checkout_mandate_id: z.string(), checkout_jwt_hash: z.string(), cart_id: z.string(), }), - execute: async (args) => { + execute: async ({ open_checkout_mandate_id, checkout_jwt_hash }) => { + const open = readIfExists(`${open_checkout_mandate_id}.sdjwt`); + if (!open) return { error: 'open_mandate_not_found', message: open_checkout_mandate_id }; + const agent = await loadOrCreateKeyPair(TEMP_DB, 'agent'); + const closed = await presentClosedMandate({ + openMandateSdJwt: open, + disclose: ['natural_language_description'], + holderPrivateJwk: agent.privateKey, + nonce: checkout_jwt_hash, // bind the presentation to this checkout + audience: CREDENTIAL_PROVIDER_AUD, + }); const id = `chk_${randomUUID()}`; - persistMandate(`${id}.sdjwt`, `stub.${Buffer.from(JSON.stringify(args)).toString('base64url')}.sig`); + persist(`${id}.sdjwt`, closed); return { checkout_mandate_chain_id: id }; }, }); export const createPaymentPresentationTool = new FunctionTool({ name: 'createPaymentPresentation', - description: 'STUB: Build the closed-payment mandate SD-JWT presentation.', + description: + 'Builds the closed-payment mandate as a KB-SD-JWT presentation whose holder binding commits to checkout_jwt_hash (the transaction binding), from the persisted open-payment mandate.', parameters: z.object({ open_payment_mandate_id: z.string(), checkout_jwt_hash: z.string(), - payment_nonce: z.string(), + payment_nonce: z.string().optional(), }), - execute: async (args) => { + execute: async ({ open_payment_mandate_id, checkout_jwt_hash }) => { + const open = readIfExists(`${open_payment_mandate_id}.sdjwt`); + if (!open) return { error: 'open_mandate_not_found', message: open_payment_mandate_id }; + const agent = await loadOrCreateKeyPair(TEMP_DB, 'agent'); + // Bind to checkout_jwt_hash (transaction id), NOT an arbitrary nonce, so + // the payment proof cannot be replayed against a different checkout. + const closed = await presentClosedMandate({ + openMandateSdJwt: open, + disclose: ['natural_language_description'], + holderPrivateJwk: agent.privateKey, + nonce: checkout_jwt_hash, + audience: CREDENTIAL_PROVIDER_AUD, + }); const id = `pay_${randomUUID()}`; - persistMandate(`${id}.sdjwt`, `stub.${Buffer.from(JSON.stringify(args)).toString('base64url')}.sig`); + persist(`${id}.sdjwt`, closed); return { payment_mandate_chain_id: id }; }, }); export const verifyCheckoutReceiptTool = new FunctionTool({ name: 'verifyCheckoutReceipt', - description: 'STUB: Verify the signed checkout receipt returned by complete_checkout.', + description: + 'Verifies a persisted closed-mandate presentation: checks the user (issuer) SD-JWT signature and the agent Key-Binding JWT against cnf.jwk, using the expected nonce (checkout_jwt_hash).', + parameters: z.object({ + mandate_chain_id: z.string(), + nonce: z.string(), + }), + execute: async ({ mandate_chain_id, nonce }) => { + const presented = readIfExists(`${mandate_chain_id}.sdjwt`); + if (!presented) return { verified: false, error: 'mandate_not_found', message: mandate_chain_id }; + const user = await loadOrCreateKeyPair(TEMP_DB, 'user'); + try { + const result = await verifyMandate({ mandateSdJwt: presented, issuerPublicJwk: user.publicKey, nonce }); + return { verified: result.keyBound, key_bound: result.keyBound }; + } catch (e) { + return { verified: false, error: 'verification_failed', message: String(e) }; + } + }, +}); + +export const resetTempDbTool = new FunctionTool({ + name: 'resetTempDb', + description: + 'Clears persisted mandate files (open/closed checkout and payment) for a fresh run. Preserves signing keys and merchant inventory.', parameters: z.object({ - receipt_signature: z.string().optional(), + confirm: z.boolean().optional(), }), - execute: async ({ receipt_signature }) => ({ verified: Boolean(receipt_signature) }), + execute: async () => { + let removed = 0; + try { + for (const f of fs.readdirSync(TEMP_DB)) { + if (MANDATE_PREFIXES.some((p) => f.startsWith(p))) { + fs.rmSync(tempPath(f), { force: true }); + removed += 1; + } + } + } catch { + return { status: 'ok', removed: 0, message: 'no temp-db to clear' }; + } + return { status: 'ok', removed, message: `removed ${removed} mandate files` }; + }, }); diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts new file mode 100644 index 00000000..d7bd19d2 --- /dev/null +++ b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Instruction for the Consent Agent (root of the shopping-agent-v2 hierarchy). + * Mirrors the intent of the Python `prompts/consent_agent.md`. + */ + +export const CONSENT_INSTRUCTION = `You are the Consent Agent, the entry point of a Human-Not-Present shopping agent. You ONLY handle delegated purchase tasks for limited / timed drops: the user authorizes you (via signed open mandates) to buy on their behalf when conditions are met. + +Classify every request first: +- MATCH: the user wants you to proxy-buy an item when a drop goes live or it falls within a budget; OR a short confirmation ("yes" / "ok" / "$200 is fine") right after you asked permission and proposed a price; OR a follow-up like "Check price now". +- NO_MATCH: anything else. Return ONLY this JSON and nothing else: + {"type":"error","error":"unsupported_task","message":"This agent only handles delegated purchase tasks for limited drops."} +A bare "yes"/"ok"/"sure" immediately after you proposed a price is always MATCH — never NO_MATCH. + +Your job: lead the user through preview -> budget confirmation -> mandate signing -> handoff to monitoring. + +Conversation memory: scan all prior messages and build active_product (full natural description: brand, item, size) and active_budget (dollars). If you proposed a price and the user only said "yes", that price is active_budget. Never re-ask for product or budget if already in the thread. + +Workflow: +A) First contact: when the user shows purchase intent for a limited/timed item, write short prose (offer to buy for them, a plausible drop time, typical price, ask their budget / permission). Do NOT call any tool yet. If the user asks to start over / reset, call resetTempDb first. +B) After the user agrees on a budget (or says "yes" to your price): call assembleAndSignMandates with: + - natural_language_description = active_product + - constraint_price_cap = active_budget + - expires_at_iso = an ISO 8601 timestamp ~1 hour from now + - allowed_merchants = optional list if the user named specific merchants + This signs the open-checkout and open-payment mandates (SD-JWTs) and returns their ids and hashes, which persist for the downstream agents. +C) Once the mandates are signed (or the user says "Check price now" and mandates already exist), transfer to the monitoring_agent so it can watch price and availability. Briefly tell the user you are now monitoring the drop. + +Tools: +- resetTempDb: clear prior mandate files when starting a fresh purchase. +- assembleAndSignMandates: sign the open mandates after the user approves the budget. +- checkConstraintsAgainstMandate: optional sanity check of price/availability against the open mandate (returns meets_constraints + violations). +- search_inventory / check_product (merchant MCP): you may use check_product to confirm an item's current price/availability, but never fabricate a price into a mandate. + +Mandate integrity: never invent prices. Transparency: explain what happened and what comes next. + +Error handling: if any tool returns an object with an "error" key, STOP and emit EXACTLY {"type":"error","error":"","message":""} as your entire response.`; diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/monitoring.ts b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/monitoring.ts new file mode 100644 index 00000000..04a3b6a2 --- /dev/null +++ b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/monitoring.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Instruction for the Monitoring Agent (sub-agent of the Consent Agent). + * Mirrors the intent of the Python `prompts/monitoring_agent.md`. + */ + +export const MONITORING_INSTRUCTION = `You are the Monitoring Agent. The open mandates have already been signed by the consent agent and persist in state. Your goal is to check the item's current price and availability against the open mandate constraints, and hand off to the purchase flow as soon as the constraints are met. + +On each turn: +1. Call check_product (merchant MCP) with the item_id and the constraint_price_cap from the open mandate to read the current price and availability. The item is usually unavailable (stock 0) until the drop fires. +2. Call checkConstraintsAgainstMandate with the open_checkout_mandate_id, the current_price, available, and (if known) the merchant — exactly as returned by check_product. It verifies the open mandate and returns { meets_constraints: boolean, violations: string[] }. +3. If meets_constraints is true AND available is true, transfer to the purchase_agent immediately so it can execute the autonomous purchase. Do not run the purchase steps yourself. +4. Otherwise (constraints not met or item not yet available), tell the user the current price/availability and that you will keep watching, then stop and wait for the next check. + +Principles: +- Mandate integrity: use only data returned by the tools; the open mandates are the source of truth for the constraints. +- Transparency: clearly report the current price, availability, and status. + +Error handling: if any tool returns an object with an "error" key, STOP and emit EXACTLY {"type":"error","error":"","message":""} as your entire response.`; diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/purchase.ts b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/purchase.ts new file mode 100644 index 00000000..08cfa76a --- /dev/null +++ b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/purchase.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Instruction for the Purchase Agent (leaf sub-agent of the Monitoring Agent). + * Mirrors the intent of the Python `prompts/purchase_agent.md`. + */ + +export const PURCHASE_INSTRUCTION = `You are the Purchase Agent. The price/availability constraints are already satisfied. Execute the full purchase pipeline autonomously — never ask the user for confirmation. + +Run these steps strictly in order. Pass each tool exactly the values returned by the previous steps; do not fabricate any value. +1. check_product (merchant MCP) — confirm the item is still available at the expected price. +2. checkConstraintsAgainstMandate(open_checkout_mandate_id, current_price, available) — re-verify; only continue if meets_constraints is true and available is true. +3. assemble_cart(item_id, qty) — build the cart. Keep the returned cart_id. +4. create_checkout(cart_id, open_checkout_mandate_id) — get the ES256-signed checkout JWT. Keep checkout_jwt, checkout_jwt_hash, and open_checkout_hash. +5. createCheckoutPresentation(open_checkout_mandate_id, checkout_jwt_hash, cart_id) — build the closed-checkout mandate bound to checkout_jwt_hash. Keep checkout_mandate_chain_id. +6. createPaymentPresentation(open_payment_mandate_id, checkout_jwt_hash) — build the closed-payment mandate; its holder binding commits to checkout_jwt_hash. Keep payment_mandate_chain_id. +7. issue_payment_credential(payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash) — the credentials provider re-verifies the payment mandate is holder-bound to this exact checkout_jwt_hash before issuing the token. Pass the SAME checkout_jwt_hash and open_checkout_hash from step 4. +8. complete_checkout(checkout_jwt, payment_credential) — hand the credential to the merchant to finalize the order. It returns TWO distinct receipts: \`payment_receipt\` (signed by the PSP) and \`checkout_receipt\` (signed by the merchant). Keep both. +9. Verify the two receipts with the matching verifier — do NOT swap them: + a. verify_payment_receipt(payment_receipt) — pass the \`payment_receipt\` field from step 8 (the PSP-signed one). It is verified against the PSP key. + b. verifyCheckoutReceipt(mandate_chain_id = the checkout_mandate_chain_id from step 5, nonce = checkout_jwt_hash from step 4) — verifies the closed checkout mandate's holder binding. +Then emit a brief purchase_complete summary (order id + both receipts) as JSON. + +Principles: +- Mandate integrity: use only data from tools; pass checkout mandates to checkout operations and payment mandates to payment operations. +- Idempotency: never repeat a tool that already succeeded. +- You do NOT have access to the PSP's initiate_payment tool; settlement is driven by issue_payment_credential + complete_checkout. + +Error handling: if ANY tool returns an object with an "error" key, immediately STOP and emit EXACTLY {"type":"error","error":"","message":""} as your entire response. Do not attempt to continue or recover.`; diff --git a/code/samples/typescript/test/unit/constraints-and-binding.test.ts b/code/samples/typescript/test/unit/constraints-and-binding.test.ts new file mode 100644 index 00000000..99bc71c4 --- /dev/null +++ b/code/samples/typescript/test/unit/constraints-and-binding.test.ts @@ -0,0 +1,92 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Covers the two fidelity upgrades: + * - constraint checker (amount + merchant allowlist) + * - hash binding: a payment presentation bound to checkout A must NOT verify + * under checkout B's hash (replay protection). + */ + +import { describe, it, expect } from 'vitest'; +import { + checkConstraints, + generateKeyPair, + issueOpenMandate, + presentClosedMandate, + verifyMandate, +} from '../../src/common/sdjwt/index.js'; + +describe('constraint checker', () => { + it('passes when price within cap and merchant allowed', () => { + const r = checkConstraints( + { price: 150, currency: 'USD', merchant: 'acme' }, + { priceCap: 200, currency: 'USD', allowedMerchants: ['acme', 'globex'] }, + ); + expect(r.meetsConstraints).toBe(true); + expect(r.violations).toEqual([]); + }); + + it('fails when price exceeds the cap', () => { + const r = checkConstraints({ price: 250 }, { priceCap: 200 }); + expect(r.meetsConstraints).toBe(false); + expect(r.violations[0]).toContain('exceeds cap'); + }); + + it('fails when merchant not in allowlist', () => { + const r = checkConstraints( + { price: 10, merchant: 'sketchy' }, + { priceCap: 100, allowedMerchants: ['acme'] }, + ); + expect(r.meetsConstraints).toBe(false); + expect(r.violations[0]).toContain('not in allowlist'); + }); + + it('ignores merchant when the mandate does not restrict merchants', () => { + const r = checkConstraints({ price: 10, merchant: 'anyone' }, { priceCap: 100 }); + expect(r.meetsConstraints).toBe(true); + }); +}); + +describe('hash binding (replay protection)', () => { + it('rejects a payment presentation under a different checkout hash', async () => { + const user = await generateKeyPair(); // issuer + const agent = await generateKeyPair(); // holder (cnf) + const checkoutHashA = 'checkout-A-hash'; + const checkoutHashB = 'checkout-B-hash'; + + const openPayment = await issueOpenMandate({ + claims: { kind: 'open_payment', constraint_price_cap: 200 }, + disclosable: [], + issuerPrivateJwk: user.privateKey, + holderPublicJwk: agent.publicKey, + }); + + // Agent presents the payment mandate bound to checkout A. + const closedForA = await presentClosedMandate({ + openMandateSdJwt: openPayment, + disclose: [], + holderPrivateJwk: agent.privateKey, + nonce: checkoutHashA, + audience: 'credentials-provider', + }); + + // Verifying under checkout A's hash succeeds and is key-bound. + const okA = await verifyMandate({ + mandateSdJwt: closedForA, + issuerPublicJwk: user.publicKey, + nonce: checkoutHashA, + }); + expect(okA.keyBound).toBe(true); + + // The same presentation must NOT verify under checkout B's hash. + await expect( + verifyMandate({ mandateSdJwt: closedForA, issuerPublicJwk: user.publicKey, nonce: checkoutHashB }), + ).rejects.toThrow(); + }); +}); diff --git a/code/samples/typescript/test/unit/credentials-token.test.ts b/code/samples/typescript/test/unit/credentials-token.test.ts new file mode 100644 index 00000000..8ae19e62 --- /dev/null +++ b/code/samples/typescript/test/unit/credentials-token.test.ts @@ -0,0 +1,68 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Phase 2: proves the credentials-provider payment token round-trips through + * SD-JWT (replacing the W3C VC token). createToken issues an ES256 SD-JWT + * credential; verifyToken cryptographically verifies it and reconstructs the + * payment method. Runs without any server. + */ + +import { describe, it, expect, beforeAll } from 'vitest'; +import { + initIssuerKey, + createToken, + updateToken, + verifyToken, +} from '../../src/roles/credentials-provider-agent/account-manager.js'; + +const EMAIL = 'bugsbunny@gmail.com'; +const ALIAS = 'American Express ending in 4444'; +const MANDATE_ID = 'pm_test_123'; + +describe('credentials-provider SD-JWT token (Phase 2)', () => { + beforeAll(async () => { + await initIssuerKey(); + }); + + it('issues an SD-JWT token (compact form, not JSON-LD VC)', async () => { + const token = await createToken(EMAIL, ALIAS); + // SD-JWT compact form is tilde-separated; a W3C VC would be a JSON object. + expect(token).toContain('~'); + expect(token.trimStart().startsWith('{')).toBe(false); + }); + + it('round-trips the payment method through createToken -> verifyToken', async () => { + const token = await createToken(EMAIL, ALIAS); + updateToken(token, MANDATE_ID); + + const pm = await verifyToken(token, MANDATE_ID); + expect(pm).not.toBeNull(); + expect(pm?.alias).toBe(ALIAS); + expect(pm?.type).toBe('CARD'); // restored from payment_method_type + // selectively-disclosable sensitive fields survive a full presentation + expect((pm as Record).token).toBe('1111000000000000'); + expect((pm as Record).cryptogram).toBe('fake_cryptogram_abc123'); + // credential metadata must NOT leak into the reconstructed payment method + expect((pm as Record).sub).toBeUndefined(); + expect((pm as Record).cnf).toBeUndefined(); + expect((pm as Record).payment_method_alias).toBeUndefined(); + }); + + it('rejects a token whose mandate binding does not match', async () => { + const token = await createToken(EMAIL, ALIAS); + updateToken(token, MANDATE_ID); + await expect(verifyToken(token, 'wrong_mandate')).rejects.toThrow('Invalid token'); + }); + + it('rejects an unknown token', async () => { + await expect(verifyToken('not-a-real-token', MANDATE_ID)).rejects.toThrow('Invalid token'); + }); +}); +// SD-JWT signature/key-binding forgery is covered at the crypto layer in +// sdjwt-mandate.test.ts ("rejects key binding signed by the wrong holder key"). diff --git a/code/samples/typescript/test/unit/es256-jwt.test.ts b/code/samples/typescript/test/unit/es256-jwt.test.ts new file mode 100644 index 00000000..d292e542 --- /dev/null +++ b/code/samples/typescript/test/unit/es256-jwt.test.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Proves the plain ES256 JWS helpers used for the merchant checkout JWT and + * the PSP payment receipt: sign, verify, and reject tampered / wrong-key tokens. + */ + +import { describe, it, expect } from 'vitest'; +import { generateKeyPair, signJwtEs256, verifyJwtEs256 } from '../../src/common/sdjwt/index.js'; + +describe('ES256 plain JWT (checkout JWT / PSP receipt)', () => { + it('signs and verifies a compact ES256 JWS', async () => { + const key = await generateKeyPair(); + const jwt = await signJwtEs256({ iss: 'merchant-agent', amount: 15000 }, key.privateKey); + expect(jwt.split('.').length).toBe(3); + const payload = await verifyJwtEs256(jwt, key.publicKey); + expect(payload.iss).toBe('merchant-agent'); + expect(payload.amount).toBe(15000); + }); + + it('rejects a token verified with the wrong key', async () => { + const signer = await generateKeyPair(); + const other = await generateKeyPair(); + const jwt = await signJwtEs256({ iss: 'merchant-payment-processor' }, signer.privateKey); + await expect(verifyJwtEs256(jwt, other.publicKey)).rejects.toThrow(); + }); + + it('rejects a tampered payload', async () => { + const key = await generateKeyPair(); + const jwt = await signJwtEs256({ status: 'settled' }, key.privateKey); + const [h, , s] = jwt.split('.'); + const forgedPayload = Buffer.from(JSON.stringify({ status: 'refunded' })).toString('base64url'); + const tampered = `${h}.${forgedPayload}.${s}`; + await expect(verifyJwtEs256(tampered, key.publicKey)).rejects.toThrow(); + }); +}); diff --git a/code/samples/typescript/test/unit/sdjwt-mandate.test.ts b/code/samples/typescript/test/unit/sdjwt-mandate.test.ts new file mode 100644 index 00000000..65a43e54 --- /dev/null +++ b/code/samples/typescript/test/unit/sdjwt-mandate.test.ts @@ -0,0 +1,125 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Spike: proves the @sd-jwt/core mandate primitives do real SD-JWT with + * selective disclosure + ES256 + cnf.jwk key binding (KB-SD-JWT) — the + * AP2 v0.2 open->closed delegation hop. Runs without Gemini / any server. + */ + +import { describe, it, expect } from 'vitest'; +import { + generateKeyPair, + issueOpenMandate, + presentClosedMandate, + verifyMandate, +} from '../../src/common/sdjwt/index.js'; + +describe('SD-JWT mandate primitives (spike)', () => { + it('issues an open mandate as a real SD-JWT with selective disclosure', async () => { + const issuer = await generateKeyPair(); // user device key + const holder = await generateKeyPair(); // agent key + + const sdjwt = await issueOpenMandate({ + claims: { + merchant: 'acme-shoes', + amount_cap: 20000, // minor units + currency: 'USD', + item: 'supershoe-gold-9', + }, + disclosable: ['amount_cap', 'item'], + issuerPrivateJwk: issuer.privateKey, + holderPublicJwk: holder.publicKey, + }); + + // Compact SD-JWT form: ~~...~ (tilde separated) + expect(sdjwt.split('~').length).toBeGreaterThan(1); + }); + + it('verifies the issuer signature (no key binding on the bare SD-JWT)', async () => { + const issuer = await generateKeyPair(); + const holder = await generateKeyPair(); + + const sdjwt = await issueOpenMandate({ + claims: { merchant: 'acme-shoes', amount_cap: 20000, item: 'x' }, + disclosable: ['amount_cap', 'item'], + issuerPrivateJwk: issuer.privateKey, + holderPublicJwk: holder.publicKey, + }); + + const result = await verifyMandate({ + mandateSdJwt: sdjwt, + issuerPublicJwk: issuer.publicKey, + }); + + expect(result.payload.merchant).toBe('acme-shoes'); + // cnf must survive (never disclosed) so the next hop can key-bind. + expect((result.payload.cnf as { jwk?: unknown }).jwk).toBeDefined(); + expect(result.keyBound).toBe(false); + }); + + it('presents a closed mandate (KB-SD-JWT) and verifies the key binding', async () => { + const issuer = await generateKeyPair(); + const holder = await generateKeyPair(); + const nonce = 'verifier-nonce-123'; + const audience = 'credentials-provider'; + + const open = await issueOpenMandate({ + claims: { merchant: 'acme-shoes', amount_cap: 20000, item: 'supershoe' }, + disclosable: ['amount_cap', 'item'], + issuerPrivateJwk: issuer.privateKey, + holderPublicJwk: holder.publicKey, + }); + + const closed = await presentClosedMandate({ + openMandateSdJwt: open, + disclose: ['amount_cap'], // reveal only the cap, keep `item` hidden + holderPrivateJwk: holder.privateKey, + nonce, + audience, + }); + + const verified = await verifyMandate({ + mandateSdJwt: closed, + issuerPublicJwk: issuer.publicKey, + nonce, + }); + + expect(verified.keyBound).toBe(true); + // selective disclosure: amount_cap revealed, item withheld + expect(verified.payload.amount_cap).toBe(20000); + expect(verified.payload.item).toBeUndefined(); + expect(verified.payload.merchant).toBe('acme-shoes'); // always-disclosed + }); + + it('rejects key binding signed by the wrong holder key', async () => { + const issuer = await generateKeyPair(); + const holder = await generateKeyPair(); + const attacker = await generateKeyPair(); + + const open = await issueOpenMandate({ + claims: { merchant: 'acme', amount_cap: 100, item: 'y' }, + disclosable: ['amount_cap'], + issuerPrivateJwk: issuer.privateKey, + holderPublicJwk: holder.publicKey, // cnf binds the real holder + }); + + // Attacker presents with their own key instead of the cnf-bound holder key. + const forged = await presentClosedMandate({ + openMandateSdJwt: open, + disclose: ['amount_cap'], + holderPrivateJwk: attacker.privateKey, + nonce: 'n', + audience: 'credentials-provider', + }); + + await expect( + verifyMandate({ mandateSdJwt: forged, issuerPublicJwk: issuer.publicKey, nonce: 'n' }), + ).rejects.toThrow(); + }); +}); From 979b93c997cc09049c914c01b935e1ea8ed83f8e Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Mon, 1 Jun 2026 17:36:27 -0500 Subject: [PATCH 05/17] feat(typescript): integrate Verifiable Intent SD-JWT chain, retire hand-rolled craft Replace the AP2 TypeScript sample's hand-rolled SD-JWT mandate code with the @verifiable-intent/core library, replicating the official Python reference flow (verifiable_intent/python/examples) layer by layer. New src/common/vi/ integration module: - fixtures, JWK key store, merchant checkout JWT, and a per-role flow facade (issueIssuerCredential / createUserMandate{Autonomous,Immediate} / createAgentFulfillment / verifyCheckoutChain / verifyPaymentChainAndConstraints) - test/unit/vi-chain.test.ts ports autonomous_flow.py + immediate_flow.py end to end (full chain, privacy-strict merchant view, over-budget rejection) Autonomous (Human-Not-Present) flow now uses the real L1->L2->split-L3 chain: - shopping-agent-v2/mandate-tools.ts: assembleAndSignMandates issues L1+L2; createMandateFulfillment builds split L3a/L3b + per-recipient L2 presentations - merchant-agent-mcp: complete_checkout verifies the checkout chain (L3b) - credentials-provider-mcp: issue_payment_credential verifies the payment chain (L3a) and enforces mandate constraints (STRICT) - consent/monitoring/purchase prompts updated to the new tool contract v1 (Human-Present): credentials-provider-agent/account-manager.ts moved off the craft onto @verifiable-intent/core SD-JWT primitives (behavior unchanged). Removed the now-dead mandate craft: common/sdjwt/{mandate-sdjwt,constraints}.ts and their unit tests; common/sdjwt keeps only shared ES256 + plain-JWS + key-store utilities. Linked @verifiable-intent/core via package.json. Verified: tsc --noEmit, build, and eslint clean; unit suite 11/11. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/package.json | 5 +- .../src/common/sdjwt/constraints.ts | 76 --- .../typescript/src/common/sdjwt/index.ts | 7 +- .../src/common/sdjwt/mandate-sdjwt.ts | 155 ------ .../typescript/src/common/vi/checkout-jwt.ts | 67 +++ .../typescript/src/common/vi/fixtures.ts | 85 +++ code/samples/typescript/src/common/vi/flow.ts | 510 ++++++++++++++++++ .../samples/typescript/src/common/vi/index.ts | 19 + code/samples/typescript/src/common/vi/keys.ts | 69 +++ .../account-manager.ts | 78 ++- .../roles/credentials-provider-mcp/server.ts | 62 +-- .../src/roles/merchant-agent-mcp/server.ts | 80 ++- .../src/roles/shopping-agent-v2/agent.ts | 6 +- .../roles/shopping-agent-v2/mandate-tools.ts | 354 +++++++----- .../shopping-agent-v2/prompts/consent.ts | 5 +- .../shopping-agent-v2/prompts/monitoring.ts | 2 +- .../shopping-agent-v2/prompts/purchase.ts | 23 +- .../test/unit/constraints-and-binding.test.ts | 92 ---- .../test/unit/sdjwt-mandate.test.ts | 125 ----- .../typescript/test/unit/vi-chain.test.ts | 267 +++++++++ 20 files changed, 1392 insertions(+), 695 deletions(-) delete mode 100644 code/samples/typescript/src/common/sdjwt/constraints.ts delete mode 100644 code/samples/typescript/src/common/sdjwt/mandate-sdjwt.ts create mode 100644 code/samples/typescript/src/common/vi/checkout-jwt.ts create mode 100644 code/samples/typescript/src/common/vi/fixtures.ts create mode 100644 code/samples/typescript/src/common/vi/flow.ts create mode 100644 code/samples/typescript/src/common/vi/index.ts create mode 100644 code/samples/typescript/src/common/vi/keys.ts delete mode 100644 code/samples/typescript/test/unit/constraints-and-binding.test.ts delete mode 100644 code/samples/typescript/test/unit/sdjwt-mandate.test.ts create mode 100644 code/samples/typescript/test/unit/vi-chain.test.ts diff --git a/code/samples/typescript/package.json b/code/samples/typescript/package.json index 7254b397..56536ab0 100644 --- a/code/samples/typescript/package.json +++ b/code/samples/typescript/package.json @@ -9,8 +9,8 @@ "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", - "cli": "npx adk run src/roles/shopping/agent.ts", - "dev": "concurrently \"npx tsx src/roles/shopping/server.ts\" \"npx tsx src/roles/credentials-provider/server.ts\" \"npx tsx src/roles/payment-processor/server.ts\" \"npx tsx src/roles/merchant/server.ts\" \"npx adk web src/roles/shopping --host localhost -p 3001\"" + "cli": "npx adk run src/roles/shopping-agent/agent.ts", + "dev": "concurrently \"npx tsx src/roles/shopping-agent/server.ts\" \"npx tsx src/roles/credentials-provider-agent/server.ts\" \"npx tsx src/roles/merchant-payment-processor-agent/server.ts\" \"npx tsx src/roles/merchant-agent/server.ts\" \"npx adk web src/web-agents --host localhost -p 3001\"" }, "keywords": [ "ap2", @@ -26,6 +26,7 @@ }, "dependencies": { "@a2a-js/sdk": "^0.3.13", + "@verifiable-intent/core": "file:../../../../verifiable-intent/typescript", "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", "@google-cloud/storage": "^7.19.0", diff --git a/code/samples/typescript/src/common/sdjwt/constraints.ts b/code/samples/typescript/src/common/sdjwt/constraints.ts deleted file mode 100644 index e74c84a8..00000000 --- a/code/samples/typescript/src/common/sdjwt/constraints.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Minimal mandate-constraint checker (amount + merchant allowlist), a TS - * subset of the Python ap2 constraint engine. The open mandate carries the - * constraints; this module checks an observed price/merchant against them. - */ - -export interface ObservedTransaction { - /** Observed unit price, in the same units as the constraint cap (USD dollars here). */ - price: number; - currency?: string; - /** Merchant identifier/name for the observed offer, if known. */ - merchant?: string; -} - -export interface MandateConstraints { - /** Maximum acceptable price (inclusive), in the same units as the observed price. */ - priceCap?: number; - currency?: string; - /** If non-empty, the merchant of the offer must be one of these. */ - allowedMerchants?: string[]; -} - -export interface ConstraintResult { - meetsConstraints: boolean; - violations: string[]; -} - -/** Check an observed transaction against the open-mandate constraints. */ -export function checkConstraints( - observed: ObservedTransaction, - constraints: MandateConstraints, -): ConstraintResult { - const violations: string[] = []; - - // Amount bound (inclusive). - if (constraints.priceCap !== undefined && observed.price > constraints.priceCap) { - violations.push( - `price ${observed.price} exceeds cap ${constraints.priceCap}`, - ); - } - - // Currency match (only when both sides specify one). - if ( - constraints.currency && - observed.currency && - constraints.currency !== observed.currency - ) { - violations.push( - `currency ${observed.currency} != allowed ${constraints.currency}`, - ); - } - - // Merchant allowlist (only enforced when the mandate restricts merchants). - if (constraints.allowedMerchants && constraints.allowedMerchants.length > 0) { - const merchant = observed.merchant; - if (!merchant) { - violations.push('merchant unknown but mandate restricts merchants'); - } else if ( - !constraints.allowedMerchants.some( - (m) => m.toLowerCase() === merchant.toLowerCase(), - ) - ) { - violations.push(`merchant ${merchant} not in allowlist`); - } - } - - return { meetsConstraints: violations.length === 0, violations }; -} diff --git a/code/samples/typescript/src/common/sdjwt/index.ts b/code/samples/typescript/src/common/sdjwt/index.ts index 9e37cd67..a7441ba3 100644 --- a/code/samples/typescript/src/common/sdjwt/index.ts +++ b/code/samples/typescript/src/common/sdjwt/index.ts @@ -7,11 +7,12 @@ * * https://www.apache.org/licenses/LICENSE-2.0 * - * Barrel for the SD-JWT mandate primitives. + * Barrel for shared ES256 primitives: keypair generation, a file-backed JWK + * key store, and plain compact JWS (used for the merchant checkout JWT and the + * PSP / merchant receipts). The Verifiable Intent SD-JWT delegation chain lives + * in `src/common/vi` (backed by @verifiable-intent/core). */ export * from './crypto.js'; -export * from './mandate-sdjwt.js'; export * from './jwt.js'; export * from './key-store.js'; -export * from './constraints.js'; diff --git a/code/samples/typescript/src/common/sdjwt/mandate-sdjwt.ts b/code/samples/typescript/src/common/sdjwt/mandate-sdjwt.ts deleted file mode 100644 index 8fb77af7..00000000 --- a/code/samples/typescript/src/common/sdjwt/mandate-sdjwt.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Real SD-JWT mandate primitives (replacing the earlier stubs), built on - * @sd-jwt/core. This mirrors the Python AP2 v0.2 reference at - * code/sdk/python/ap2/sdk/sdjwt/ (sd_jwt.py + kb_sd_jwt.py): - * - * - issueOpenMandate: issuer signs an SD-JWT with selectively-disclosable - * claims and a `cnf.jwk` holder-binding key (RFC 7800). - * - presentClosedMandate: the holder produces a KB-SD-JWT (Key-Binding JWT) - * proving possession of the `cnf` key, bound to a - * nonce + audience (the open->closed delegation hop). - * - verifyMandate: verifies the issuer signature and, when a KB-JWT is - * present, verifies it against the `cnf.jwk` extracted - * from the issuer payload — the same cnf-walk the - * Python chain verifier performs. - */ - -import { SDJwtInstance } from '@sd-jwt/core'; -import type { DisclosureFrame, KbVerifier, PresentationFrame } from '@sd-jwt/types'; -import { - ALG, - hasher, - saltGenerator, - makeSigner, - makeVerifier, - type Es256Jwk, -} from './crypto.js'; - -export interface OpenMandateInput { - /** The mandate body (constraints, expiry, description, etc.). */ - claims: Record; - /** Top-level claim keys to make selectively disclosable. */ - disclosable: string[]; - /** Issuer (e.g. user device) private key that signs the mandate. */ - issuerPrivateJwk: Es256Jwk; - /** Holder (e.g. agent) public key bound into `cnf.jwk` for key binding. */ - holderPublicJwk: Es256Jwk; -} - -/** Issue an open mandate as an SD-JWT with a `cnf.jwk` holder-binding key. */ -export async function issueOpenMandate(input: OpenMandateInput): Promise { - const signer = await makeSigner(input.issuerPrivateJwk); - const sdjwt = new SDJwtInstance({ - signer, - signAlg: ALG, - hasher, - hashAlg: 'sha-256', - saltGenerator, - }); - const payload = { - ...input.claims, - // cnf is NEVER selectively disclosed — it must always be present so a - // verifier can key-bind the next hop (RFC 7800). - cnf: { jwk: input.holderPublicJwk }, - }; - const disclosureFrame = { _sd: input.disclosable } as DisclosureFrame; - return sdjwt.issue(payload, disclosureFrame); -} - -export interface PresentInput { - /** The open mandate SD-JWT to present. */ - openMandateSdJwt: string; - /** Disclosable claim keys to reveal in this presentation. */ - disclose: string[]; - /** Holder private key — MUST correspond to the SD-JWT's `cnf.jwk`. */ - holderPrivateJwk: Es256Jwk; - /** Replay-protection nonce supplied by the verifier. */ - nonce: string; - /** Intended audience of the presentation. */ - audience: string; - /** Seconds since epoch for the KB-JWT `iat`. Defaults to now. */ - issuedAt?: number; -} - -/** Produce a KB-SD-JWT: a holder-bound presentation of the open mandate. */ -export async function presentClosedMandate(input: PresentInput): Promise { - const kbSigner = await makeSigner(input.holderPrivateJwk); - const sdjwt = new SDJwtInstance({ - hasher, - hashAlg: 'sha-256', - saltGenerator, - kbSigner, - kbSignAlg: ALG, - }); - const presentationFrame = Object.fromEntries( - input.disclose.map((k) => [k, true]), - ) as PresentationFrame>; - return sdjwt.present(input.openMandateSdJwt, presentationFrame, { - kb: { - payload: { - iat: input.issuedAt ?? Math.floor(Date.now() / 1000), - aud: input.audience, - nonce: input.nonce, - }, - }, - }); -} - -export interface VerifyInput { - mandateSdJwt: string; - /** Issuer public key to check the SD-JWT signature. */ - issuerPublicJwk: Es256Jwk; - /** Required if the presentation carries a KB-JWT. */ - nonce?: string; -} - -export interface VerifyResult { - payload: Record; - /** True when a valid Key-Binding JWT was verified against `cnf.jwk`. */ - keyBound: boolean; -} - -/** Verify the issuer signature and, if present, the KB-JWT against cnf.jwk. */ -export async function verifyMandate(input: VerifyInput): Promise { - const verifier = await makeVerifier(input.issuerPublicJwk); - - // Decode first to pull the holder key out of `cnf.jwk` — exactly what the - // Python chain verifier does to resolve each hop's key. - const decodeInst = new SDJwtInstance({ hasher, hashAlg: 'sha-256' }); - const decoded = await decodeInst.decode(input.mandateSdJwt); - const issuerPayload = (decoded.jwt?.payload ?? {}) as Record; - const cnf = issuerPayload.cnf as { jwk?: Es256Jwk } | undefined; - - let kbVerifier: KbVerifier | undefined; - if (cnf?.jwk) { - const holderVerify = await makeVerifier(cnf.jwk); - kbVerifier = (data: string, sig: string) => holderVerify(data, sig); - } - - const sdjwt = new SDJwtInstance({ - hasher, - hashAlg: 'sha-256', - verifier, - kbVerifier, - }); - - const result = await sdjwt.verify( - input.mandateSdJwt, - input.nonce ? { keyBindingNonce: input.nonce } : undefined, - ); - - return { - payload: result.payload as Record, - keyBound: 'kb' in result && result.kb !== undefined, - }; -} - -export { generateKeyPair } from './crypto.js'; diff --git a/code/samples/typescript/src/common/vi/checkout-jwt.ts b/code/samples/typescript/src/common/vi/checkout-jwt.ts new file mode 100644 index 00000000..8149e6f0 --- /dev/null +++ b/code/samples/typescript/src/common/vi/checkout-jwt.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Merchant-signed checkout JWT — a plain ES256 compact JWS committing the cart. + * Port of python/examples/helpers.py `create_checkout_jwt` / `checkout_hash_from_jwt`. + * The checkout hash binds the Payment Mandate to the checkout (transaction_id) + * and the Layer 3 fulfillment to the merchant cart. + */ + +import { hashBytes, jwtEncode, makeSigner, utf8 } from '@verifiable-intent/core'; +import { findProduct } from './fixtures.js'; +import type { ViKeyPair } from './keys.js'; + +export interface CartLineItem { + sku: string; + quantity?: number; +} + +/** Build and sign a merchant checkout JWT from line items (ES256, merchant key). */ +export async function createCheckoutJwt(items: CartLineItem[], merchant: ViKeyPair): Promise { + const now = Math.floor(Date.now() / 1000); + const cartItems: Record[] = []; + let totalCents = 0; + + for (const item of items) { + const product = findProduct(item.sku); + if (!product) { + throw new Error(`Product ${item.sku} not found`); + } + const qty = item.quantity ?? 1; + totalCents += product.price * qty; + cartItems.push({ + sku: product.sku, + name: product.name, + size: product.size, + size_label: product.sizeLabel, + color: product.color, + quantity: qty, + unitPrice: product.price / 100, // display price in dollars + }); + } + + const payload = { + iss: 'https://tennis-warehouse.com', + sub: 'cart_checkout', + iat: now, + exp: now + 3600, + cart: { + items: cartItems, + subTotal: { amount: totalCents / 100, currencyCode: 'USD' }, + }, + }; + const header = { alg: 'ES256', typ: 'JWT', kid: merchant.kid }; + const signer = await makeSigner(merchant.privateKey); + return jwtEncode(header, payload, signer); +} + +/** SHA-256(base64url) of a checkout JWT string — the checkout hash / transaction id. */ +export function checkoutHashFromJwt(checkoutJwt: string): string { + return hashBytes(utf8(checkoutJwt)); +} diff --git a/code/samples/typescript/src/common/vi/fixtures.ts b/code/samples/typescript/src/common/vi/fixtures.ts new file mode 100644 index 00000000..0e9a4221 --- /dev/null +++ b/code/samples/typescript/src/common/vi/fixtures.ts @@ -0,0 +1,85 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Scenario fixtures for the Verifiable Intent flow — a direct port of the + * `verifiable_intent` Python reference examples (python/examples/helpers.py): + * a tennis-racket purchase across two acceptable merchants. These define the + * canonical catalog, merchant allowlist, acceptable items and payment + * instrument used by the autonomous (3-layer) and immediate (2-layer) flows. + */ + +import type { Dict } from '@verifiable-intent/core'; + +/** Merchant allowlist — carried into the open mandate constraints + disclosures. */ +export const MERCHANTS: Dict[] = [ + { id: 'merchant-uuid-1', name: 'Tennis Warehouse', website: 'https://tennis-warehouse.com' }, + { id: 'merchant-uuid-2', name: 'Babolat', website: 'https://babolat.com' }, +]; + +/** Acceptable items — the user's line-item constraint references these by id. */ +export const ACCEPTABLE_ITEMS: Dict[] = [ + { id: 'BAB86345', title: 'Babolat Pure Aero Tennis Racket' }, + { id: 'HEA23102', title: 'Head Graphene 360 Speed' }, +]; + +export interface Product { + sku: string; + name: string; + /** Price in minor units (cents). */ + price: number; + currency: string; + brand: string; + model: string; + color: string; + size: number; + sizeLabel: string; + category: string; +} + +export const PRODUCTS: Product[] = [ + { + sku: 'BAB86345', + name: 'Babolat Pure Aero Tennis Racket', + price: 27999, + currency: 'USD', + brand: 'Babolat', + model: 'Pure Aero', + color: 'white', + size: 3, + sizeLabel: '4 3/8', + category: 'racket', + }, + { + sku: 'HEA23102', + name: 'Head Graphene 360 Speed Tennis Racket', + price: 24999, + currency: 'USD', + brand: 'HEAD', + model: 'Speed Pro', + color: 'black', + size: 3, + sizeLabel: '4 3/8', + category: 'racket', + }, +]; + +/** Mastercard digital card payment instrument (matches the Python reference). */ +export const PAYMENT_INSTRUMENT: Dict = { + type: 'mastercard.srcDigitalCard', + id: 'f199c3dd-7106-478b-9b5f-7af9ca725170', + description: 'Mastercard **** 1234', +}; + +export function getCatalog(): Product[] { + return PRODUCTS; +} + +export function findProduct(sku: string): Product | undefined { + return PRODUCTS.find((p) => p.sku === sku); +} diff --git a/code/samples/typescript/src/common/vi/flow.ts b/code/samples/typescript/src/common/vi/flow.ts new file mode 100644 index 00000000..81086a56 --- /dev/null +++ b/code/samples/typescript/src/common/vi/flow.ts @@ -0,0 +1,510 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Verifiable Intent flow — per-role facade over `@verifiable-intent/core`. + * + * This is the TypeScript replication of the official Python integration + * (verifiable_intent: python/examples/autonomous_flow.py + immediate_flow.py), + * sliced along the AP2 role boundaries so each agent/MCP server calls exactly + * the one layer it owns: + * + * Issuer (credentials-provider) → issueIssuerCredential (L1) + * User (shopping-agent-v2) → createUserMandate{Autonomous,Immediate} (L2) + * Agent (shopping-agent-v2) → createAgentFulfillment (L3a + L3b + presentations) + * Merchant(merchant-agent-mcp) → verifyCheckoutChain (verify L3b) + * Network (merchant-pp-mcp) → verifyPaymentChainAndConstraints (verify L3a + constraints) + * + * All values that cross a process boundary are serialized SD-JWT strings. + */ + +import { randomUUID } from 'node:crypto'; +import { + AllowedMerchantConstraint, + AllowedPayeeConstraint, + ChainVerificationResult, + CheckoutL3Mandate, + CheckoutLineItemsConstraint, + CheckoutMandate, + type Constraint, + ConstraintCheckResult, + createLayer1, + createLayer2Autonomous, + createLayer2Immediate, + createLayer3Checkout, + createLayer3Payment, + type Dict, + decodeSdJwt, + type Es256Jwk, + FinalCheckoutMandate, + FinalPaymentMandate, + hashAscii, + hashDisclosure, + IssuerCredential, + MandateMode, + PaymentAmountConstraint, + PaymentL3Mandate, + PaymentMandate, + PaymentRecurrenceConstraint, + resolveDisclosures, + type SdJwt, + StrictnessMode, + buildSelectivePresentation, + checkConstraints, + UserMandate, + verifyChain, +} from '@verifiable-intent/core'; + +import { checkoutHashFromJwt } from './checkout-jwt.js'; +import type { ViKeyPair } from './keys.js'; + +const nowSeconds = (): number => Math.floor(Date.now() / 1000); +const asJwk = (jwk: Es256Jwk): Dict => ({ ...(jwk as unknown as Dict) }); + +// --------------------------------------------------------------------------- +// L1 — Issuer credential (Credentials Provider) +// --------------------------------------------------------------------------- + +export interface IssueCredentialParams { + /** User device public key bound into the credential via cnf.jwk. */ + userPublicJwk: Es256Jwk; + /** Issuer (Credentials Provider) signing keypair. */ + issuer: ViKeyPair; + /** Subject (cardholder) identifier. */ + sub: string; + iss?: string; + aud?: string | null; + iat?: number; + ttlSeconds?: number; + email?: string | null; + panLastFour?: string; + scheme?: string; +} + +/** Issue the Layer 1 issuer credential (binds the user's key). Returns serialized SD-JWT. */ +export async function issueIssuerCredential(params: IssueCredentialParams): Promise { + const iat = params.iat ?? nowSeconds(); + const credential = new IssuerCredential({ + iss: params.iss ?? 'https://www.mastercard.com', + sub: params.sub, + iat, + exp: iat + (params.ttlSeconds ?? 86400), + aud: params.aud ?? 'https://wallet.example.com', + cnfJwk: asJwk(params.userPublicJwk), + email: params.email ?? null, + panLastFour: params.panLastFour ?? '', + scheme: params.scheme ?? 'Mastercard', + }); + const l1 = await createLayer1(credential, params.issuer.privateKey, { kid: params.issuer.kid }); + return l1.serialize(); +} + +// --------------------------------------------------------------------------- +// L2 — User mandate, autonomous (human-not-present) +// --------------------------------------------------------------------------- + +export interface AutonomousMandateParams { + l1Serialized: string; + user: ViKeyPair; + /** Agent public key the user delegates to (bound via cnf.jwk). */ + agentPublicJwk: Es256Jwk; + /** Agent kid — MUST equal the agent's L3 header kid. */ + agentKid: string; + promptSummary: string; + nonce?: string; + aud?: string; + iss?: string; + iat?: number; + ttlSeconds?: number; + merchants: Dict[]; + acceptableItems: Dict[]; + paymentInstrument: Dict; + riskData?: Dict | null; + /** Amount range in minor units (cents). */ + amountMin: number; + amountMax: number; + currency?: string; + /** Override the line-items constraint; defaults to one line of acceptableItems. */ + lineItems?: Dict[]; + recurrence?: { frequency: string; startDate: string; endDate?: string | null; number?: number | null }; +} + +/** Create the Layer 2 autonomous user mandate (open mandates + agent delegation). */ +export async function createUserMandateAutonomous(params: AutonomousMandateParams): Promise { + const iat = params.iat ?? nowSeconds(); + const currency = params.currency ?? 'USD'; + + const checkoutConstraints = [ + new AllowedMerchantConstraint({ allowed: params.merchants }), + new CheckoutLineItemsConstraint({ + items: params.lineItems ?? [{ id: 'line-item-1', acceptable_items: params.acceptableItems, quantity: 1 }], + }), + ]; + + const paymentConstraints: Constraint[] = [ + new PaymentAmountConstraint({ currency, min: params.amountMin, max: params.amountMax }), + new AllowedPayeeConstraint({ allowed: params.merchants }), + ]; + if (params.recurrence) { + paymentConstraints.push( + new PaymentRecurrenceConstraint({ + frequency: params.recurrence.frequency, + startDate: params.recurrence.startDate, + endDate: params.recurrence.endDate ?? null, + number: params.recurrence.number ?? null, + }), + ); + } + + const mandate = new UserMandate({ + nonce: params.nonce ?? randomUUID(), + aud: params.aud ?? 'https://agent.verifiable-intent.example', + iat, + iss: params.iss ?? 'https://wallet.example.com', + exp: iat + (params.ttlSeconds ?? 86400), + mode: MandateMode.AUTONOMOUS, + sdHash: hashAscii(params.l1Serialized), + promptSummary: params.promptSummary, + checkoutMandate: new CheckoutMandate({ + vct: 'mandate.checkout.open.1', + cnfJwk: asJwk(params.agentPublicJwk), + cnfKid: params.agentKid, + constraints: checkoutConstraints, + }), + paymentMandate: new PaymentMandate({ + vct: 'mandate.payment.open.1', + cnfJwk: asJwk(params.agentPublicJwk), + cnfKid: params.agentKid, + paymentInstrument: params.paymentInstrument, + riskData: params.riskData ?? null, + constraints: paymentConstraints, + }), + merchants: params.merchants, + acceptableItems: params.acceptableItems, + }); + + const l2 = await createLayer2Autonomous(mandate, params.user.privateKey, { kid: params.user.kid }); + return l2.serialize(); +} + +// --------------------------------------------------------------------------- +// L2 — User mandate, immediate (human-present) +// --------------------------------------------------------------------------- + +export interface ImmediateMandateParams { + l1Serialized: string; + user: ViKeyPair; + checkoutJwt: string; + paymentInstrument: Dict; + payee: Dict; + /** Amount in minor units (cents). */ + amount: number; + currency?: string; + nonce?: string; + aud?: string; + iss?: string; + iat?: number; + ttlSeconds?: number; + promptSummary?: string | null; +} + +/** Create the Layer 2 immediate user mandate (final values, no delegation, no L3). */ +export async function createUserMandateImmediate(params: ImmediateMandateParams): Promise { + const iat = params.iat ?? nowSeconds(); + const checkoutHash = checkoutHashFromJwt(params.checkoutJwt); + + const mandate = new UserMandate({ + nonce: params.nonce ?? randomUUID(), + aud: params.aud ?? 'https://agent.verifiable-intent.example', + iat, + iss: params.iss ?? 'https://wallet.example.com', + exp: iat + (params.ttlSeconds ?? 900), + mode: MandateMode.IMMEDIATE, + sdHash: hashAscii(params.l1Serialized), + promptSummary: params.promptSummary ?? null, + checkoutMandate: new CheckoutMandate({ + vct: 'mandate.checkout.1', + checkoutJwt: params.checkoutJwt, + checkoutHash, + }), + paymentMandate: new PaymentMandate({ + vct: 'mandate.payment.1', + paymentInstrument: params.paymentInstrument, + payee: params.payee, + currency: params.currency ?? 'USD', + amount: params.amount, + transactionId: checkoutHash, + }), + }); + + const result = await createLayer2Immediate(mandate, params.user.privateKey, { kid: params.user.kid }); + return result.serialize(); +} + +// --------------------------------------------------------------------------- +// L3 — Agent fulfillment (split L3a payment + L3b checkout) + selective routing +// --------------------------------------------------------------------------- + +/** Find the disclosure string in an L2 whose resolved object value matches `predicate`. */ +function findDisclosure(l2: SdJwt, predicate: (value: Dict) => boolean): string | null { + for (let i = 0; i < l2.disclosures.length; i++) { + const dv = l2.disclosureValues[i]; + const value = dv.length ? dv[dv.length - 1] : null; + if (value && typeof value === 'object' && !Array.isArray(value) && predicate(value as Dict)) { + return l2.disclosures[i]; + } + } + return null; +} + +export interface AgentFulfillmentParams { + l2Serialized: string; + agent: ViKeyPair; + checkoutJwt: string; + checkoutHash: string; + /** The chosen merchant (must be one of the mandate's merchants). */ + payee: Dict; + /** The chosen item id / sku (must be an acceptable item). */ + itemId: string; + /** Amount in minor units (cents). */ + amount: number; + currency?: string; + paymentInstrument: Dict; + networkAud?: string; + merchantAud?: string; + nonce?: string; + iat?: number; + ttlSeconds?: number; +} + +export interface AgentFulfillment { + /** L3a — payment fulfillment for the network. */ + l3PaymentSerialized: string; + /** L3b — checkout fulfillment for the merchant. */ + l3CheckoutSerialized: string; + /** L2 presentation the network sees (payment + merchant disclosures). */ + l2PaymentSerialized: string; + /** L2 presentation the merchant sees (checkout + item disclosures). */ + l2CheckoutSerialized: string; +} + +/** Agent builds the split L3 credentials and the per-recipient L2 presentations. */ +export async function createAgentFulfillment(params: AgentFulfillmentParams): Promise { + const iat = params.iat ?? nowSeconds(); + const exp = iat + (params.ttlSeconds ?? 300); + const nonce = params.nonce ?? randomUUID(); + + const l2 = decodeSdJwt(params.l2Serialized); + const l2BaseJwt = params.l2Serialized.split('~')[0]; + + const paymentDisc = findDisclosure(l2, (v) => v.vct === 'mandate.payment.open.1'); + const checkoutDisc = findDisclosure(l2, (v) => v.vct === 'mandate.checkout.open.1'); + const merchantDisc = findDisclosure(l2, (v) => + Boolean(v.website) && (params.payee.id ? v.id === params.payee.id : v.name === params.payee.name), + ); + const itemDisc = findDisclosure(l2, (v) => v.id === params.itemId || v.sku === params.itemId); + + if (!paymentDisc || !checkoutDisc || !merchantDisc || !itemDisc) { + throw new Error( + `Missing L2 disclosures (payment=${Boolean(paymentDisc)} checkout=${Boolean(checkoutDisc)} ` + + `merchant=${Boolean(merchantDisc)} item=${Boolean(itemDisc)})`, + ); + } + + // L3a — payment, for the network. + const l3aMandate = new PaymentL3Mandate({ + nonce, + aud: params.networkAud ?? 'https://www.mastercard.com', + iat, + iss: 'https://agent.example.com', + exp, + finalPayment: new FinalPaymentMandate({ + transactionId: params.checkoutHash, + payee: params.payee, + paymentAmount: { currency: params.currency ?? 'USD', amount: params.amount }, + paymentInstrument: params.paymentInstrument, + }), + finalMerchant: params.payee, + }); + const l3a = await createLayer3Payment(l3aMandate, params.agent.privateKey, l2BaseJwt, paymentDisc, merchantDisc, { + kid: params.agent.kid, + }); + + // L3b — checkout, for the merchant. + const l3bMandate = new CheckoutL3Mandate({ + nonce, + aud: params.merchantAud ?? 'https://tennis-warehouse.com', + iat, + iss: 'https://agent.example.com', + exp, + finalCheckout: new FinalCheckoutMandate({ checkoutJwt: params.checkoutJwt, checkoutHash: params.checkoutHash }), + }); + const l3b = await createLayer3Checkout(l3bMandate, params.agent.privateKey, l2BaseJwt, checkoutDisc, itemDisc, { + kid: params.agent.kid, + }); + + const l2PaymentSerialized = buildSelectivePresentation(l2BaseJwt, [paymentDisc, merchantDisc]); + const l2CheckoutSerialized = buildSelectivePresentation(l2BaseJwt, [checkoutDisc, itemDisc]); + + return { + l3PaymentSerialized: l3a.serialize(), + l3CheckoutSerialized: l3b.serialize(), + l2PaymentSerialized, + l2CheckoutSerialized, + }; +} + +// --------------------------------------------------------------------------- +// Verification — Merchant (checkout side) +// --------------------------------------------------------------------------- + +export interface VerifyCheckoutParams { + l1Serialized: string; + /** L2 presentation the merchant received (checkout + item disclosures). */ + l2CheckoutSerialized: string; + l3CheckoutSerialized: string; + issuerPublicJwk: Es256Jwk; + /** Full L2 serialization (for pairing); defaults to the checkout presentation. */ + l2Serialized?: string; + currentTime?: number; + expectedL3CheckoutAud?: string; + expectedL3CheckoutNonce?: string; +} + +/** Merchant verifies the checkout-side chain (L1 → L2 → L3b). */ +export async function verifyCheckoutChain(params: VerifyCheckoutParams): Promise { + const l1 = decodeSdJwt(params.l1Serialized); + const l2 = decodeSdJwt(params.l2CheckoutSerialized); + const l3Checkout = decodeSdJwt(params.l3CheckoutSerialized); + return verifyChain(l1, l2, { + l3Checkout, + issuerPublicJwk: params.issuerPublicJwk, + l1Serialized: params.l1Serialized, + l2Serialized: params.l2Serialized ?? params.l2CheckoutSerialized, + l2CheckoutSerialized: params.l2CheckoutSerialized, + currentTime: params.currentTime, + expectedL3CheckoutAud: params.expectedL3CheckoutAud, + expectedL3CheckoutNonce: params.expectedL3CheckoutNonce, + }); +} + +// --------------------------------------------------------------------------- +// Verification — Network / PSP (payment side) + constraint enforcement +// --------------------------------------------------------------------------- + +export interface VerifyPaymentParams { + l1Serialized: string; + /** L2 presentation the network received (payment + merchant disclosures). */ + l2PaymentSerialized: string; + l3PaymentSerialized: string; + issuerPublicJwk: Es256Jwk; + /** Full L2 serialization (for pairing); defaults to the payment presentation. */ + l2Serialized?: string; + currentTime?: number; + expectedL3PaymentAud?: string; + expectedL3PaymentNonce?: string; +} + +export interface PaymentVerificationOutcome { + valid: boolean; + errors: string[]; + result: ChainVerificationResult; + constraints: ConstraintCheckResult | null; +} + +/** Network verifies the payment-side chain (L1 → L2 → L3a) and enforces constraints (STRICT). */ +export async function verifyPaymentChainAndConstraints( + params: VerifyPaymentParams, +): Promise { + const l1 = decodeSdJwt(params.l1Serialized); + const l2 = decodeSdJwt(params.l2Serialized ?? params.l2PaymentSerialized); + const l3Payment = decodeSdJwt(params.l3PaymentSerialized); + + const result = await verifyChain(l1, l2, { + l3Payment, + issuerPublicJwk: params.issuerPublicJwk, + l1Serialized: params.l1Serialized, + l2Serialized: params.l2Serialized ?? params.l2PaymentSerialized, + l2PaymentSerialized: params.l2PaymentSerialized, + currentTime: params.currentTime, + expectedL3PaymentAud: params.expectedL3PaymentAud, + expectedL3PaymentNonce: params.expectedL3PaymentNonce, + }); + + if (!result.valid) { + return { valid: false, errors: result.errors, result, constraints: null }; + } + + const constraints = enforcePaymentConstraints(l2, result); + if (constraints === null) { + // No payment constraints present (e.g. immediate mode) — chain validity stands. + return { valid: true, errors: [], result, constraints: null }; + } + return { + valid: constraints.satisfied, + errors: constraints.satisfied ? [] : constraints.violations, + result, + constraints, + }; +} + +/** + * Resolve L2 payment constraints + L3 fulfillment and run the constraint checker + * in STRICT mode (payment networks MUST treat unknown constraints as violations). + * Port of python/examples/helpers.py `validate_intent` / autonomous_flow step 8. + * Returns null when the L2 carries no payment constraints. + */ +function enforcePaymentConstraints(l2: SdJwt, result: ChainVerificationResult): ConstraintCheckResult | null { + const l2Claims = resolveDisclosures(l2); + const delegates = (l2Claims.delegate_payload as Dict[] | undefined) ?? []; + + let paymentConstraints: Dict[] = []; + for (const delegate of delegates) { + if ( + delegate && + typeof delegate === 'object' && + (delegate.vct === 'mandate.payment.open.1' || delegate.vct === 'mandate.payment.1') + ) { + paymentConstraints = (delegate.constraints as Dict[] | undefined) ?? []; + break; + } + } + if (paymentConstraints.length === 0) { + return null; + } + + let fulfillment: Dict = {}; + for (const delegate of (result.l3PaymentClaims.delegate_payload as Dict[] | undefined) ?? []) { + if (delegate && typeof delegate === 'object' && delegate.vct === 'mandate.payment.1') { + fulfillment = { ...delegate }; + break; + } + } + + // Resolve allowed_payees SD-refs into concrete merchant objects for the checker. + const discByHash = new Map(); + for (let i = 0; i < l2.disclosures.length; i++) { + discByHash.set(hashDisclosure(l2.disclosures[i]), l2.disclosureValues[i]); + } + for (const constraint of paymentConstraints) { + if (constraint.type === 'mandate.payment.allowed_payees') { + const resolved: unknown[] = []; + for (const ref of (constraint.allowed as unknown[] | undefined) ?? []) { + const refHash = ref && typeof ref === 'object' ? ((ref as Dict)['...'] as string) : ''; + if (refHash && discByHash.has(refHash)) { + const dv = discByHash.get(refHash)!; + resolved.push(dv[dv.length - 1]); + } + } + fulfillment.allowed_merchants = resolved; + break; + } + } + + return checkConstraints(paymentConstraints, fulfillment, { mode: StrictnessMode.STRICT }); +} diff --git a/code/samples/typescript/src/common/vi/index.ts b/code/samples/typescript/src/common/vi/index.ts new file mode 100644 index 00000000..c5fc9956 --- /dev/null +++ b/code/samples/typescript/src/common/vi/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Verifiable Intent integration barrel. Re-exports the `@verifiable-intent/core` + * primitives plus the AP2-sample glue (fixtures, key store, checkout JWT, and + * the per-role flow facade) that replicate the Python reference integration. + */ + +export * from '@verifiable-intent/core'; +export * from './fixtures.js'; +export * from './keys.js'; +export * from './checkout-jwt.js'; +export * from './flow.js'; diff --git a/code/samples/typescript/src/common/vi/keys.ts b/code/samples/typescript/src/common/vi/keys.ts new file mode 100644 index 00000000..a48273d3 --- /dev/null +++ b/code/samples/typescript/src/common/vi/keys.ts @@ -0,0 +1,69 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * File-backed ES256 (P-256) JWK key store for the Verifiable Intent roles. + * Each role (issuer, user, agent, merchant, psp) persists its keypair as + * `_key.jwk.json` under a shared TEMP_DB so the agent and each MCP + * subprocess resolve the same public keys and can verify each other's + * signatures. Keys carry a stable `kid` matching the Python reference, used + * for the SD-JWT header `kid` and the `cnf.jwk` key-binding (RFC 7800). + */ + +import fs from 'node:fs'; +import path from 'node:path'; +import { generateEs256Key, type Es256Jwk } from '@verifiable-intent/core'; + +export interface ViKeyPair { + publicKey: Es256Jwk; + privateKey: Es256Jwk; + kid: string; +} + +/** Stable per-role key ids, mirroring python/examples/helpers.py. */ +export const ROLE_KIDS: Record = { + issuer: 'mastercard-issuer-key-1', + user: 'user-device-key-1', + agent: 'agent-key-1', + merchant: 'merchant-key-1', + psp: 'psp-key-1', +}; + +function keyPath(tempDb: string, name: string): string { + return path.join(tempDb, `${name}_key.jwk.json`); +} + +function kidFor(name: string): string { + return ROLE_KIDS[name] ?? `${name}-key-1`; +} + +/** Load the named keypair, generating + persisting it (with a stable kid) on first use. */ +export async function loadOrCreateViKey(tempDb: string, name: string): Promise { + try { + const raw = JSON.parse(fs.readFileSync(keyPath(tempDb, name), 'utf-8')) as Partial; + if (raw.publicKey && raw.privateKey) { + return { publicKey: raw.publicKey, privateKey: raw.privateKey, kid: raw.kid ?? kidFor(name) }; + } + } catch { + /* fall through to generate */ + } + const { publicKey, privateKey } = await generateEs256Key(); + const pair: ViKeyPair = { publicKey, privateKey, kid: kidFor(name) }; + fs.mkdirSync(tempDb, { recursive: true }); + fs.writeFileSync(keyPath(tempDb, name), JSON.stringify(pair)); + return pair; +} + +/** Read just the public JWK of a previously persisted keypair (null if absent). */ +export function loadViPublicJwk(tempDb: string, name: string): Es256Jwk | null { + try { + return (JSON.parse(fs.readFileSync(keyPath(tempDb, name), 'utf-8')) as ViKeyPair).publicKey; + } catch { + return null; + } +} diff --git a/code/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts b/code/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts index 2bbb8fa1..6a24cc95 100644 --- a/code/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts +++ b/code/samples/typescript/src/roles/credentials-provider-agent/account-manager.ts @@ -22,14 +22,23 @@ * * Token creation issues SD-JWT payment credentials signed with the * credentials-provider's ES256 key (AP2 v0.2). Sensitive payment method - * fields are made selectively disclosable. + * fields are made selectively disclosable. Built directly on the Verifiable + * Intent SD-JWT primitives (@verifiable-intent/core): the issuer signs a + * selective-disclosure SD-JWT with a `cnf.jwk` holder-binding key (RFC 7800), + * and verification checks the issuer signature and resolves the disclosures. */ import { - issueOpenMandate, - verifyMandate, - generateKeyPair, - type Es256KeyPair, -} from '../../common/sdjwt/index.js'; + createDisclosure, + createSdArray, + createSdJwt, + decodeSdJwt, + type Es256Jwk, + generateEs256Key, + resolveDisclosures, + verifySdJwtSignature, +} from '../../common/vi/index.js'; + +type IssuerKeyPair = { publicKey: Es256Jwk; privateKey: Es256Jwk }; export type PaymentMethod = { type: string; @@ -181,7 +190,7 @@ const tokens: { * public key is also bound into `cnf.jwk` as the holder key for these demo * tokens (no separate holder key is involved at issuance time). */ -let issuerKey: Es256KeyPair | null = null; +let issuerKey: IssuerKeyPair | null = null; /** * Initializes the issuer ES256 keypair. Idempotent — repeated calls after the @@ -191,7 +200,7 @@ export async function initIssuerKey(): Promise { if (issuerKey) { return; } - issuerKey = await generateKeyPair(); + issuerKey = await generateEs256Key(); } /** @@ -199,7 +208,7 @@ export async function initIssuerKey(): Promise { * * @throws Error if {@link initIssuerKey} has not been called yet. */ -function getIssuerKey(): Es256KeyPair { +function getIssuerKey(): IssuerKeyPair { if (!issuerKey) { throw new Error('Issuer key not initialized. Call initIssuerKey() first.'); } @@ -262,14 +271,25 @@ export const createToken = async ( // declared as selectively disclosable. const disclosable = DISCLOSABLE_FIELDS.filter((field) => field in claims); - // Issue an SD-JWT payment credential signed by the issuer key. The issuer's - // public key is also bound as the holder key (cnf.jwk). - const token = await issueOpenMandate({ - claims, - disclosable, - issuerPrivateJwk: key.privateKey, - holderPublicJwk: key.publicKey, - }); + // Build the selective disclosures for the sensitive fields, and the always- + // visible payload for everything else (plus the cnf holder-binding key). + const disclosures = disclosable.map((field) => createDisclosure(field, claims[field])); + const payload: Record = {}; + for (const [k, v] of Object.entries(claims)) { + if (!disclosable.includes(k)) { + payload[k] = v; + } + } + // cnf is never selectively disclosed (RFC 7800). For these demo credentials + // the issuer's own public key is bound as the holder key. + payload.cnf = { jwk: key.publicKey }; + payload._sd = createSdArray(disclosures); + payload._sd_alg = 'sha-256'; + + // Issue + serialize the SD-JWT payment credential signed by the issuer key. + const header = { alg: 'ES256', typ: 'sd+jwt' }; + const sdJwt = await createSdJwt(header, payload, disclosures, key.privateKey); + const token = sdJwt.serialize(); tokens[token] = { emailAddress, @@ -324,23 +344,29 @@ export const verifyToken = async ( const key = getIssuerKey(); - // Cryptographically verify the SD-JWT issuer signature. - const { payload } = await verifyMandate({ - mandateSdJwt: token, - issuerPublicJwk: key.publicKey, - }); + // Cryptographically verify the SD-JWT issuer signature, then resolve the + // selective disclosures into the full claim set. + const sdJwt = decodeSdJwt(token); + const signatureValid = await verifySdJwtSignature(sdJwt, key.publicKey); + if (!signatureValid) { + throw new Error('Invalid token: SD-JWT signature verification failed'); + } + const payload = resolveDisclosures(sdJwt); // Reconstruct the PaymentMethod from the verified claims. Strip the - // credential metadata (sub, payment_method_alias, type, iat) and the - // holder-binding `cnf` claim. The credential `type` ("PaymentCredential") - // is dropped; the payment method's own `type` (e.g. "CARD") was stashed in - // `payment_method_type` at issuance and is restored here. + // credential metadata (sub, payment_method_alias, type, iat), the SD-JWT + // machinery (_sd, _sd_alg), and the holder-binding `cnf` claim. The + // credential `type` ("PaymentCredential") is dropped; the payment method's + // own `type` (e.g. "CARD") was stashed in `payment_method_type` at issuance + // and is restored here. const { sub: _sub, payment_method_alias: _alias, type: _credentialType, iat: _iat, cnf: _cnf, + _sd: _sdHashes, + _sd_alg: _sdAlg, payment_method_type: paymentMethodType, ...rest } = payload; diff --git a/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts b/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts index ac76467a..6dace1f3 100644 --- a/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts +++ b/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts @@ -26,7 +26,8 @@ import { randomUUID } from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import { z } from 'zod'; -import { verifyMandate, verifyJwtEs256, loadPublicJwk, type Es256KeyPair } from '../../common/sdjwt/index.js'; +import { verifyJwtEs256, loadPublicJwk } from '../../common/sdjwt/index.js'; +import { loadViPublicJwk, verifyPaymentChainAndConstraints } from '../../common/vi/index.js'; const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; @@ -40,13 +41,6 @@ function readTempFile(filename: string): string | null { } } -/** The user (issuer) public key persisted by the shopping agent's mandate-tools. */ -function loadUserPublicJwk(): JsonWebKey | null { - const raw = readTempFile('user_key.jwk.json'); - if (!raw) return null; - return (JSON.parse(raw) as Es256KeyPair).publicKey; -} - const server = new McpServer({ name: 'credentials-provider-mcp', version: '0.2.0', @@ -56,56 +50,52 @@ server.registerTool( 'issue_payment_credential', { description: - 'Issue a scoped single-use payment token for a verified payment mandate chain, ' + - 'bound to the open-checkout and checkout-JWT hashes and a payment nonce.', + 'Verify the payment-side Verifiable Intent chain (L1 issuer -> L2 user mandate -> L3a agent ' + + 'payment fulfillment) and enforce the mandate constraints (STRICT). Only on a valid chain ' + + 'whose amount/payee satisfy the user mandate is a scoped single-use payment token issued.', inputSchema: { payment_mandate_chain_id: z.string(), - open_checkout_hash: z.string(), - checkout_jwt_hash: z.string(), - payment_nonce: z.string().optional(), }, }, - async ({ payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash }) => { - if (!payment_mandate_chain_id || !open_checkout_hash || !checkout_jwt_hash) { - const error = { - error: 'missing_fields', - message: 'payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash are required', - }; + async ({ payment_mandate_chain_id }) => { + if (!payment_mandate_chain_id) { + const error = { error: 'missing_fields', message: 'payment_mandate_chain_id is required' }; return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true, }; } - // Load the presented closed payment-mandate (KB-SD-JWT) the agent persisted. - const presented = readTempFile(`${payment_mandate_chain_id}.sdjwt`); - if (!presented) { - const error = { error: 'mandate_not_found', message: payment_mandate_chain_id }; + // Load the agent-produced payment chain artifacts (L3a + L2 payment presentation) + L1. + const l3Payment = readTempFile(`${payment_mandate_chain_id}.sdjwt`); + const l2Payment = readTempFile(`${payment_mandate_chain_id}.l2.sdjwt`); + const l1 = readTempFile('l1.sdjwt'); + if (!l3Payment || !l2Payment || !l1) { + const error = { error: 'payment_chain_artifacts_missing', message: payment_mandate_chain_id }; return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; } - const issuerPublicJwk = loadUserPublicJwk(); + const issuerPublicJwk = loadViPublicJwk(TEMP_DB, 'issuer'); if (!issuerPublicJwk) { - const error = { error: 'issuer_key_unavailable', message: 'user_key.jwk.json not found in TEMP_DB' }; + const error = { error: 'issuer_key_unavailable', message: 'issuer key not found in TEMP_DB' }; return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; } - // Verify the issuer (user) SD-JWT signature AND the agent Key-Binding JWT - // against cnf.jwk, requiring the holder binding to commit to the SPECIFIC - // checkout (nonce === checkout_jwt_hash). This is the hash-binding check: - // a payment presentation made for a different checkout is rejected here, - // and a forged/unbound presentation fails signature/KB verification. + // Verify the payment chain (issuer sig, L2->L1 binding, agent L3 key + sd_hash + // binding) AND enforce the user mandate constraints in STRICT mode. A forged + // chain fails signature/binding; an out-of-policy amount/payee fails constraints. try { - const { keyBound } = await verifyMandate({ - mandateSdJwt: presented, + const outcome = await verifyPaymentChainAndConstraints({ + l1Serialized: l1, + l2PaymentSerialized: l2Payment, + l3PaymentSerialized: l3Payment, issuerPublicJwk, - nonce: checkout_jwt_hash, }); - if (!keyBound) { - const error = { error: 'checkout_binding_failed', message: 'payment mandate not holder-bound to this checkout_jwt_hash' }; + if (!outcome.valid) { + const error = { error: 'payment_chain_invalid', message: outcome.errors.join('; ') }; return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; } } catch (e) { - const error = { error: 'mandate_verification_failed', message: String(e) }; + const error = { error: 'payment_chain_verification_failed', message: String(e) }; return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; } diff --git a/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts b/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts index e2c2b8f5..e54553f3 100644 --- a/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts +++ b/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts @@ -30,6 +30,7 @@ import { loadOrCreateKeyPair, loadPublicJwk, } from '../../common/sdjwt/index.js'; +import { loadViPublicJwk, verifyCheckoutChain } from '../../common/vi/index.js'; /** Base64url SHA-256 — the checkout JWT hash binds the payment mandate. */ function sha256Base64Url(input: string): string { @@ -137,6 +138,15 @@ function getCart(cartId: string): unknown { return loadCarts()[cartId]; } +/** Read a raw text file from TEMP_DB (chain artifacts), null if absent. */ +function readTempText(filename: string): string | null { + try { + return fs.readFileSync(path.join(TEMP_DB, filename), 'utf-8'); + } catch { + return null; + } +} + const server = new McpServer({ name: 'merchant-mcp', version: '0.2.0', @@ -275,14 +285,17 @@ server.registerTool( 'create_checkout', { description: - 'Create a checkout for a previously assembled cart, binding it to an ' + - 'open checkout mandate and returning a (stubbed) signed checkout JWT and its hash.', + 'Create a checkout for a previously assembled cart, returning a real ES256-signed ' + + 'merchant checkout JWT and its hash. The agent commits checkout_jwt_hash into the ' + + 'Layer 3 fulfillment (it becomes the payment transaction_id and the checkout binding).', inputSchema: { cart_id: z.string(), - open_checkout_mandate_id: z.string(), + // Accepted for backwards-compat with older prompts; the VI chain binds via + // checkout_jwt_hash, so the open-mandate id is no longer required here. + open_checkout_mandate_id: z.string().optional(), }, }, - async ({ cart_id, open_checkout_mandate_id }) => { + async ({ cart_id }) => { const cart = getCart(cart_id); if (!cart) { const error = { error: 'cart_not_found' }; @@ -292,28 +305,15 @@ server.registerTool( isError: true, }; } - // Hash the referenced open checkout mandate so the checkout JWT commits to - // it (downstream binding). The agent persisted it under TEMP_DB. - let openCheckoutHash: string | null = null; - try { - const openMandate = fs.readFileSync(path.join(TEMP_DB, `${open_checkout_mandate_id}.sdjwt`), 'utf-8'); - openCheckoutHash = sha256Base64Url(openMandate); - } catch { - const error = { error: 'open_mandate_not_found', message: open_checkout_mandate_id }; - return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; - } - // Sign a real ES256 checkout JWT with the merchant key. A random `jti` - // gives the JWT entropy (the AP2 spec warns deterministic signatures over - // low-entropy checkout content are rainbow-table-able). The JWT commits to - // the open checkout mandate via open_checkout_hash. + // Sign a real ES256 checkout JWT with the merchant key. A random `jti` gives + // the JWT entropy (the AP2 spec warns deterministic signatures over + // low-entropy checkout content are rainbow-table-able for the hash binding). const merchant = await loadOrCreateKeyPair(TEMP_DB, 'merchant'); const checkoutJwt = await signJwtEs256( { iss: 'merchant-agent', iat: Math.floor(Date.now() / 1000), jti: randomUUID(), - open_checkout_mandate_id, - open_checkout_hash: openCheckoutHash, cart, }, merchant.privateKey, @@ -322,8 +322,6 @@ server.registerTool( const result = { checkout_jwt: checkoutJwt, checkout_jwt_hash: checkoutJwtHash, - open_checkout_hash: openCheckoutHash, - open_checkout_mandate_id, cart, }; return { @@ -337,15 +335,17 @@ server.registerTool( 'complete_checkout', { description: - 'Complete a checkout using a checkout JWT and optional payment credential, ' + - 'returning a (stubbed) signed checkout receipt.', + 'Verify the checkout-side Verifiable Intent chain (L1 issuer -> L2 user mandate -> L3b agent ' + + 'checkout fulfillment) for the given chain id, then settle with the PSP and return a ' + + 'merchant-signed checkout receipt plus the PSP-signed payment receipt.', inputSchema: { checkout_jwt: z.string(), payment_credential: z.unknown().optional(), + checkout_mandate_chain_id: z.string(), }, }, - async ({ checkout_jwt, payment_credential }) => { - // Verify the merchant's own checkout JWT signature before finalizing. + async ({ checkout_jwt, payment_credential, checkout_mandate_chain_id }) => { + // 1. Verify the merchant's own checkout JWT signature before finalizing. const merchantPub = loadPublicJwk(TEMP_DB, 'merchant'); if (!merchantPub) { const error = { error: 'merchant_key_unavailable', message: 'merchant key not found in TEMP_DB' }; @@ -360,6 +360,34 @@ server.registerTool( return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; } + // 2. Verify the checkout-side VI chain the agent produced (L1 -> L2 -> L3b). + const l1 = readTempText('l1.sdjwt'); + const l3Checkout = readTempText(`${checkout_mandate_chain_id}.sdjwt`); + const l2Checkout = readTempText(`${checkout_mandate_chain_id}.l2.sdjwt`); + const issuerPub = loadViPublicJwk(TEMP_DB, 'issuer'); + if (!l1 || !l3Checkout || !l2Checkout || !issuerPub) { + const error = { + error: 'checkout_chain_artifacts_missing', + message: `missing L1/L2/L3b for ${checkout_mandate_chain_id}`, + }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + try { + const chain = await verifyCheckoutChain({ + l1Serialized: l1, + l2CheckoutSerialized: l2Checkout, + l3CheckoutSerialized: l3Checkout, + issuerPublicJwk: issuerPub, + }); + if (!chain.valid) { + const error = { error: 'checkout_chain_invalid', message: chain.errors.join('; ') }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + } catch (e) { + const error = { error: 'checkout_chain_verification_failed', message: String(e) }; + return { content: [{ type: 'text', text: JSON.stringify(error) }], structuredContent: error, isError: true }; + } + // Settle with the PSP (server-to-server) and obtain a PSP-signed payment // receipt — mirrors Python merchant_agent_mcp._initiate_payment_with_payment_processor. let paymentReceipt: string | null = null; diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/agent.ts b/code/samples/typescript/src/roles/shopping-agent-v2/agent.ts index 2269cae8..7ecfae24 100644 --- a/code/samples/typescript/src/roles/shopping-agent-v2/agent.ts +++ b/code/samples/typescript/src/roles/shopping-agent-v2/agent.ts @@ -34,8 +34,7 @@ import { fileURLToPath } from 'node:url'; import { assembleAndSignMandatesTool, checkConstraintsAgainstMandateTool, - createCheckoutPresentationTool, - createPaymentPresentationTool, + createMandateFulfillmentTool, verifyCheckoutReceiptTool, resetTempDbTool, } from './mandate-tools.js'; @@ -130,8 +129,7 @@ export const purchaseAgent = new LlmAgent({ outputKey: 'purchase_result', tools: [ checkConstraintsAgainstMandateTool, - createCheckoutPresentationTool, - createPaymentPresentationTool, + createMandateFulfillmentTool, verifyCheckoutReceiptTool, makeMcpToolset(MERCHANT_SERVER), makeMcpToolset(CREDENTIAL_SERVER), diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts b/code/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts index 90dfb0dc..1fce4a34 100644 --- a/code/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts +++ b/code/samples/typescript/src/roles/shopping-agent-v2/mandate-tools.ts @@ -7,41 +7,62 @@ * * https://www.apache.org/licenses/LICENSE-2.0 * - * Mandate helper tools used by shopping-agent-v2, backed by real SD-JWT - * (src/common/sdjwt, @sd-jwt/core + ES256): - * - assembleAndSignMandates: issues open-checkout + open-payment SD-JWTs with - * a cnf.jwk agent key (RFC 7800). Constraints (price cap, merchant - * allowlist) are carried as claims so they can be verified later. - * - checkConstraintsAgainstMandate: verifies the open mandate and checks the - * observed price/merchant against its constraints (real, not a stub). - * - createCheckout/PaymentPresentation: KB-SD-JWT presentations whose holder - * binding commits to the checkout_jwt_hash (so a presentation cannot be - * replayed against a different checkout). - * - verifyCheckoutReceipt: verifies a presented mandate's signature + KB. + * Mandate helper tools for shopping-agent-v2, backed by the Verifiable Intent + * library (src/common/vi -> @verifiable-intent/core). This replicates the + * official Python reference flow (verifiable_intent/python/examples) sliced + * along the AP2 roles this agent owns: the User (Layer 2) and the Agent + * (Layer 3 split fulfillment). + * + * - assembleAndSignMandates: issues the Layer 1 issuer credential (binding the + * user key) and the Layer 2 autonomous user mandate (binding the agent key, + * carrying the amount-range / merchant / line-item constraints). + * - checkConstraintsAgainstMandate: reads the L2 constraints and checks an + * observed price/merchant against them. + * - createMandateFulfillment: the agent builds the split Layer 3 credentials + * (L3a payment for the network, L3b checkout for the merchant) plus the + * per-recipient L2 presentations, all bound to the checkout hash. + * - verifyCheckoutReceipt: verifies a merchant-signed checkout receipt (JWS). * - resetTempDb: clears mandate files for a fresh run, preserving keys. * - * Names mirror Python `shopping_agent.mandate_tools`. + * Chain artifacts (serialized SD-JWTs) are persisted under TEMP_DB and referenced + * by id so the merchant / credentials-provider MCP servers can verify them. */ import { FunctionTool } from '@google/adk'; import fs from 'node:fs'; import path from 'node:path'; -import { randomUUID, createHash } from 'node:crypto'; +import { randomUUID } from 'node:crypto'; import { z } from 'zod'; import { - issueOpenMandate, - presentClosedMandate, - verifyMandate, - loadOrCreateKeyPair, + ACCEPTABLE_ITEMS, + MERCHANTS, + PAYMENT_INSTRUMENT, + type Dict, + type ViKeyPair, + b64urlDecode, checkConstraints, -} from '../../common/sdjwt/index.js'; + createAgentFulfillment, + createUserMandateAutonomous, + decodeSdJwt, + es256Verify, + hashAscii, + hashDisclosure, + issueIssuerCredential, + jwtDecodeParts, + loadOrCreateViKey, + loadViPublicJwk, + resolveDisclosures, + StrictnessMode, +} from '../../common/vi/index.js'; const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; -const CREDENTIAL_PROVIDER_AUD = 'credentials-provider'; -/** Mandate file prefixes — cleared by resetTempDb, never the *_key.jwk.json keys. */ -const MANDATE_PREFIXES = ['open_chk_', 'open_pay_', 'chk_', 'pay_']; +/** Singleton chain artifacts for the active run. */ +const L1_FILE = 'l1.sdjwt'; +const L2_FILE = 'l2.sdjwt'; +/** Mandate file prefixes cleared by resetTempDb (never the *_key.jwk.json keys). */ +const MANDATE_PREFIXES = ['l1', 'l2', 'chk_', 'pay_']; function tempPath(filename: string): string { return path.join(TEMP_DB, filename); @@ -60,60 +81,95 @@ function readIfExists(filename: string): string | null { } } -function sha256Base64Url(input: string): string { - return createHash('sha256').update(input).digest('base64url'); +/** Verify a plain ES256 compact JWS and return its payload. */ +async function verifyJws(token: string, publicJwk: ViKeyPair['publicKey']): Promise { + const parts = token.split('.'); + if (parts.length !== 3) { + throw new Error('malformed JWT: expected three dot-separated segments'); + } + const ok = await es256Verify(`${parts[0]}.${parts[1]}`, b64urlDecode(parts[2]), publicJwk); + if (!ok) { + throw new Error('invalid JWT signature'); + } + return jwtDecodeParts(token).payload as Dict; +} + +/** Build the merchant allowlist (Dict[]) the L2 mandate carries. */ +function resolveMerchants(allowedNames?: string[]): Dict[] { + if (!allowedNames || allowedNames.length === 0) { + return MERCHANTS; + } + return allowedNames.map((name) => { + const known = MERCHANTS.find((m) => String(m.name).toLowerCase() === name.toLowerCase()); + return known ?? { name, website: `https://${name.toLowerCase().replace(/[^a-z0-9]+/g, '-')}.example` }; + }); } export const assembleAndSignMandatesTool = new FunctionTool({ name: 'assembleAndSignMandates', description: - 'Builds and signs the open-checkout and open-payment mandates as real SD-JWTs (ES256) with a cnf.jwk agent key for key binding. The price cap and optional merchant allowlist are carried as mandate constraints. Persists them under TEMP_DB and returns their IDs and sd-hashes.', + 'Issues the Layer 1 issuer credential (binding the user key) and signs the Layer 2 autonomous ' + + 'user mandate as a real SD-JWT delegation chain (ES256). The mandate delegates to the agent key ' + + '(cnf.jwk) and carries the amount-range, allowed-merchant and acceptable-item constraints. ' + + 'Persists L1 + L2 under TEMP_DB and returns their ids and hashes.', parameters: z.object({ natural_language_description: z.string(), constraint_price_cap: z.number(), expires_at_iso: z.string(), allowed_merchants: z.array(z.string()).optional(), - user_cart_confirmation_required: z.boolean().optional(), + item_id: z.string().optional(), + item_title: z.string().optional(), }), execute: async (args) => { - const user = await loadOrCreateKeyPair(TEMP_DB, 'user'); - const agent = await loadOrCreateKeyPair(TEMP_DB, 'agent'); + const issuer = await loadOrCreateViKey(TEMP_DB, 'issuer'); + const user = await loadOrCreateViKey(TEMP_DB, 'user'); + const agent = await loadOrCreateViKey(TEMP_DB, 'agent'); - // Constraints are always-present claims (not selectively disclosed) so the - // monitoring agent can read them back when checking the open mandate. - const baseClaims = { - natural_language_description: args.natural_language_description, - constraint_price_cap: args.constraint_price_cap, - allowed_merchants: args.allowed_merchants ?? [], - currency: 'USD', - expires_at_iso: args.expires_at_iso, - user_cart_confirmation_required: args.user_cart_confirmation_required ?? true, - }; + const now = Math.floor(Date.now() / 1000); + const expiresAt = Math.floor(new Date(args.expires_at_iso).getTime() / 1000); + const ttlSeconds = Number.isFinite(expiresAt) && expiresAt > now ? expiresAt - now : 3600; + const amountMaxCents = Math.round(args.constraint_price_cap * 100); - const openCheckoutId = `open_chk_${randomUUID()}`; - const openPaymentId = `open_pay_${randomUUID()}`; + const merchants = resolveMerchants(args.allowed_merchants); + const acceptableItems: Dict[] = args.item_id + ? [{ id: args.item_id, title: args.item_title ?? args.natural_language_description }] + : ACCEPTABLE_ITEMS; - const openCheckout = await issueOpenMandate({ - claims: { ...baseClaims, kind: 'open_checkout' }, - disclosable: ['natural_language_description'], - issuerPrivateJwk: user.privateKey, - holderPublicJwk: agent.publicKey, + // L1 — the Issuer (credentials provider) binds the user's key. + const l1 = await issueIssuerCredential({ + userPublicJwk: user.publicKey, + issuer, + sub: 'shopping-agent-user', + iat: now, + panLastFour: '1234', }); - const openPayment = await issueOpenMandate({ - claims: { ...baseClaims, kind: 'open_payment' }, - disclosable: ['natural_language_description'], - issuerPrivateJwk: user.privateKey, - holderPublicJwk: agent.publicKey, + + // L2 — the User delegates to the agent with constraints (autonomous mode). + const l2 = await createUserMandateAutonomous({ + l1Serialized: l1, + user, + agentPublicJwk: agent.publicKey, + agentKid: agent.kid, + promptSummary: args.natural_language_description, + iat: now, + ttlSeconds, + merchants, + acceptableItems, + paymentInstrument: PAYMENT_INSTRUMENT, + amountMin: 0, + amountMax: amountMaxCents, }); - persist(`${openCheckoutId}.sdjwt`, openCheckout); - persist(`${openPaymentId}.sdjwt`, openPayment); + persist(L1_FILE, l1); + persist(L2_FILE, l2); return { - open_checkout_mandate_id: openCheckoutId, - open_checkout_hash: sha256Base64Url(openCheckout), - open_payment_mandate_id: openPaymentId, - open_payment_hash: sha256Base64Url(openPayment), + credential_id: 'l1', + mandate_id: 'l2', + l1_hash: hashAscii(l1), + l2_hash: hashAscii(l2), + amount_cap_cents: amountMaxCents, + currency: 'USD', }; }, }); @@ -121,122 +177,146 @@ export const assembleAndSignMandatesTool = new FunctionTool({ export const checkConstraintsAgainstMandateTool = new FunctionTool({ name: 'checkConstraintsAgainstMandate', description: - "Verifies the open checkout mandate and checks the observed price (and optional merchant) against the mandate's constraints (price cap, merchant allowlist). Returns meets_constraints + any violations.", + "Reads the signed Layer 2 user mandate and checks an observed price (and optional merchant) " + + "against its amount-range and allowed-payee constraints. Returns meets_constraints + violations.", parameters: z.object({ - open_checkout_mandate_id: z.string(), current_price: z.number(), available: z.boolean(), merchant: z.string().optional(), currency: z.string().optional(), }), - execute: async ({ open_checkout_mandate_id, current_price, available, merchant, currency }) => { + execute: async ({ current_price, available, merchant, currency }) => { if (!available) { return { meets_constraints: false, satisfies: false, violations: ['item_not_available'] }; } - const open = readIfExists(`${open_checkout_mandate_id}.sdjwt`); - if (!open) { - return { error: 'open_mandate_not_found', message: open_checkout_mandate_id }; + const l2Serialized = readIfExists(L2_FILE); + if (!l2Serialized) { + return { error: 'mandate_not_found', message: 'l2 mandate not signed yet' }; } - const user = await loadOrCreateKeyPair(TEMP_DB, 'user'); - let claims: Record; + + let l2; try { - claims = (await verifyMandate({ mandateSdJwt: open, issuerPublicJwk: user.publicKey })).payload; + l2 = decodeSdJwt(l2Serialized); } catch (e) { - return { error: 'mandate_verification_failed', message: String(e) }; + return { error: 'mandate_decode_failed', message: String(e) }; + } + const claims = resolveDisclosures(l2); + const delegates = (claims.delegate_payload as Dict[] | undefined) ?? []; + let paymentConstraints: Dict[] = []; + for (const d of delegates) { + if (d && typeof d === 'object' && d.vct === 'mandate.payment.open.1') { + paymentConstraints = (d.constraints as Dict[] | undefined) ?? []; + break; + } } - const priceCap = typeof claims.constraint_price_cap === 'number' ? claims.constraint_price_cap : undefined; - const allowedMerchants = Array.isArray(claims.allowed_merchants) - ? (claims.allowed_merchants as string[]) - : undefined; - - const { meetsConstraints, violations } = checkConstraints( - { price: current_price, currency, merchant }, - { priceCap, currency: typeof claims.currency === 'string' ? claims.currency : undefined, allowedMerchants }, - ); + // Build a candidate fulfillment from the observed offer and resolve allowed_payees. + const fulfillment: Dict = { + payment_amount: { currency: currency ?? 'USD', amount: Math.round(current_price * 100) }, + }; + const discByHash = new Map(); + for (let i = 0; i < l2.disclosures.length; i++) { + discByHash.set(hashDisclosure(l2.disclosures[i]), l2.disclosureValues[i]); + } + for (const c of paymentConstraints) { + if (c.type === 'mandate.payment.allowed_payees') { + const resolved: unknown[] = []; + for (const ref of (c.allowed as unknown[] | undefined) ?? []) { + const refHash = ref && typeof ref === 'object' ? ((ref as Dict)['...'] as string) : ''; + if (refHash && discByHash.has(refHash)) { + const dv = discByHash.get(refHash)!; + resolved.push(dv[dv.length - 1]); + } + } + fulfillment.allowed_merchants = merchant ? [{ name: merchant }] : resolved; + } + } + const result = checkConstraints(paymentConstraints, fulfillment, { mode: StrictnessMode.PERMISSIVE }); return { - meets_constraints: meetsConstraints, - satisfies: meetsConstraints, // alias for older prompt wording - violations, - price_cap: priceCap, + meets_constraints: result.satisfied, + satisfies: result.satisfied, // alias for older prompt wording + violations: result.violations, price: current_price, available, }; }, }); -export const createCheckoutPresentationTool = new FunctionTool({ - name: 'createCheckoutPresentation', +export const createMandateFulfillmentTool = new FunctionTool({ + name: 'createMandateFulfillment', description: - 'Builds the closed-checkout mandate as a KB-SD-JWT presentation whose holder binding commits to checkout_jwt_hash, from the persisted open-checkout mandate.', + 'Builds the split Layer 3 agent fulfillment from the signed L2 mandate: L3a (payment, for the ' + + 'network) and L3b (checkout, for the merchant), each bound to the checkout hash, plus the ' + + 'per-recipient L2 presentations (selective disclosure). Persists them and returns their chain ids.', parameters: z.object({ - open_checkout_mandate_id: z.string(), + checkout_jwt: z.string(), checkout_jwt_hash: z.string(), - cart_id: z.string(), + item_id: z.string(), + amount_cents: z.number(), + payee_name: z.string().optional(), + currency: z.string().optional(), }), - execute: async ({ open_checkout_mandate_id, checkout_jwt_hash }) => { - const open = readIfExists(`${open_checkout_mandate_id}.sdjwt`); - if (!open) return { error: 'open_mandate_not_found', message: open_checkout_mandate_id }; - const agent = await loadOrCreateKeyPair(TEMP_DB, 'agent'); - const closed = await presentClosedMandate({ - openMandateSdJwt: open, - disclose: ['natural_language_description'], - holderPrivateJwk: agent.privateKey, - nonce: checkout_jwt_hash, // bind the presentation to this checkout - audience: CREDENTIAL_PROVIDER_AUD, - }); - const id = `chk_${randomUUID()}`; - persist(`${id}.sdjwt`, closed); - return { checkout_mandate_chain_id: id }; - }, -}); + execute: async ({ checkout_jwt, checkout_jwt_hash, item_id, amount_cents, payee_name, currency }) => { + const l2Serialized = readIfExists(L2_FILE); + if (!l2Serialized) { + return { error: 'mandate_not_found', message: 'l2 mandate not signed yet' }; + } + const agent = await loadOrCreateViKey(TEMP_DB, 'agent'); -export const createPaymentPresentationTool = new FunctionTool({ - name: 'createPaymentPresentation', - description: - 'Builds the closed-payment mandate as a KB-SD-JWT presentation whose holder binding commits to checkout_jwt_hash (the transaction binding), from the persisted open-payment mandate.', - parameters: z.object({ - open_payment_mandate_id: z.string(), - checkout_jwt_hash: z.string(), - payment_nonce: z.string().optional(), - }), - execute: async ({ open_payment_mandate_id, checkout_jwt_hash }) => { - const open = readIfExists(`${open_payment_mandate_id}.sdjwt`); - if (!open) return { error: 'open_mandate_not_found', message: open_payment_mandate_id }; - const agent = await loadOrCreateKeyPair(TEMP_DB, 'agent'); - // Bind to checkout_jwt_hash (transaction id), NOT an arbitrary nonce, so - // the payment proof cannot be replayed against a different checkout. - const closed = await presentClosedMandate({ - openMandateSdJwt: open, - disclose: ['natural_language_description'], - holderPrivateJwk: agent.privateKey, - nonce: checkout_jwt_hash, - audience: CREDENTIAL_PROVIDER_AUD, - }); - const id = `pay_${randomUUID()}`; - persist(`${id}.sdjwt`, closed); - return { payment_mandate_chain_id: id }; + const payee = + (payee_name && MERCHANTS.find((m) => String(m.name).toLowerCase() === payee_name.toLowerCase())) || + MERCHANTS[0]; + + let fulfillment; + try { + fulfillment = await createAgentFulfillment({ + l2Serialized, + agent, + checkoutJwt: checkout_jwt, + checkoutHash: checkout_jwt_hash, + payee, + itemId: item_id, + amount: amount_cents, + currency: currency ?? 'USD', + paymentInstrument: PAYMENT_INSTRUMENT, + }); + } catch (e) { + return { error: 'fulfillment_failed', message: String(e) }; + } + + const checkoutId = `chk_${randomUUID()}`; + const paymentId = `pay_${randomUUID()}`; + persist(`${checkoutId}.sdjwt`, fulfillment.l3CheckoutSerialized); + persist(`${checkoutId}.l2.sdjwt`, fulfillment.l2CheckoutSerialized); + persist(`${paymentId}.sdjwt`, fulfillment.l3PaymentSerialized); + persist(`${paymentId}.l2.sdjwt`, fulfillment.l2PaymentSerialized); + + return { + checkout_mandate_chain_id: checkoutId, + payment_mandate_chain_id: paymentId, + }; }, }); export const verifyCheckoutReceiptTool = new FunctionTool({ name: 'verifyCheckoutReceipt', description: - 'Verifies a persisted closed-mandate presentation: checks the user (issuer) SD-JWT signature and the agent Key-Binding JWT against cnf.jwk, using the expected nonce (checkout_jwt_hash).', + 'Verifies a merchant-signed checkout receipt (compact ES256 JWS) against the merchant public key. ' + + 'Returns verified + the receipt id and issuer.', parameters: z.object({ - mandate_chain_id: z.string(), - nonce: z.string(), + checkout_receipt: z.string(), }), - execute: async ({ mandate_chain_id, nonce }) => { - const presented = readIfExists(`${mandate_chain_id}.sdjwt`); - if (!presented) return { verified: false, error: 'mandate_not_found', message: mandate_chain_id }; - const user = await loadOrCreateKeyPair(TEMP_DB, 'user'); + execute: async ({ checkout_receipt }) => { + const merchantPub = loadViPublicJwk(TEMP_DB, 'merchant'); + if (!merchantPub) { + return { verified: false, error: 'merchant_key_unavailable', message: 'merchant key not found in TEMP_DB' }; + } try { - const result = await verifyMandate({ mandateSdJwt: presented, issuerPublicJwk: user.publicKey, nonce }); - return { verified: result.keyBound, key_bound: result.keyBound }; + const payload = await verifyJws(checkout_receipt, merchantPub); + return { verified: true, receipt_id: payload.receipt_id, issuer: payload.iss }; } catch (e) { - return { verified: false, error: 'verification_failed', message: String(e) }; + return { verified: false, error: 'receipt_verification_failed', message: String(e) }; } }, }); @@ -244,7 +324,8 @@ export const verifyCheckoutReceiptTool = new FunctionTool({ export const resetTempDbTool = new FunctionTool({ name: 'resetTempDb', description: - 'Clears persisted mandate files (open/closed checkout and payment) for a fresh run. Preserves signing keys and merchant inventory.', + 'Clears persisted mandate / chain files (L1, L2, and the L3 fulfillment chains) for a fresh run. ' + + 'Preserves signing keys and merchant inventory.', parameters: z.object({ confirm: z.boolean().optional(), }), @@ -252,7 +333,8 @@ export const resetTempDbTool = new FunctionTool({ let removed = 0; try { for (const f of fs.readdirSync(TEMP_DB)) { - if (MANDATE_PREFIXES.some((p) => f.startsWith(p))) { + if (f.endsWith('_key.jwk.json')) continue; + if (MANDATE_PREFIXES.some((p) => f.startsWith(p)) && (f.endsWith('.sdjwt') || f === L1_FILE || f === L2_FILE)) { fs.rmSync(tempPath(f), { force: true }); removed += 1; } diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts index d7bd19d2..f56b594c 100644 --- a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts +++ b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts @@ -30,7 +30,10 @@ B) After the user agrees on a budget (or says "yes" to your price): call assembl - constraint_price_cap = active_budget - expires_at_iso = an ISO 8601 timestamp ~1 hour from now - allowed_merchants = optional list if the user named specific merchants - This signs the open-checkout and open-payment mandates (SD-JWTs) and returns their ids and hashes, which persist for the downstream agents. + - item_id = the merchant catalog item id, if you already have one from search_inventory / check_product + This issues the Layer 1 issuer credential and signs the Layer 2 autonomous user mandate (a real + SD-JWT delegation chain) with the amount-range, merchant and acceptable-item constraints, and + returns the credential_id / mandate_id and hashes, which persist for the downstream agents. C) Once the mandates are signed (or the user says "Check price now" and mandates already exist), transfer to the monitoring_agent so it can watch price and availability. Briefly tell the user you are now monitoring the drop. Tools: diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/monitoring.ts b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/monitoring.ts index 04a3b6a2..e3fe1db3 100644 --- a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/monitoring.ts +++ b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/monitoring.ts @@ -15,7 +15,7 @@ export const MONITORING_INSTRUCTION = `You are the Monitoring Agent. The open ma On each turn: 1. Call check_product (merchant MCP) with the item_id and the constraint_price_cap from the open mandate to read the current price and availability. The item is usually unavailable (stock 0) until the drop fires. -2. Call checkConstraintsAgainstMandate with the open_checkout_mandate_id, the current_price, available, and (if known) the merchant — exactly as returned by check_product. It verifies the open mandate and returns { meets_constraints: boolean, violations: string[] }. +2. Call checkConstraintsAgainstMandate with the current_price, available, and (if known) the merchant — exactly as returned by check_product. It reads the signed L2 user mandate and returns { meets_constraints: boolean, violations: string[] }. 3. If meets_constraints is true AND available is true, transfer to the purchase_agent immediately so it can execute the autonomous purchase. Do not run the purchase steps yourself. 4. Otherwise (constraints not met or item not yet available), tell the user the current price/availability and that you will keep watching, then stop and wait for the next check. diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/purchase.ts b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/purchase.ts index 08cfa76a..b7378889 100644 --- a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/purchase.ts +++ b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/purchase.ts @@ -14,21 +14,20 @@ export const PURCHASE_INSTRUCTION = `You are the Purchase Agent. The price/availability constraints are already satisfied. Execute the full purchase pipeline autonomously — never ask the user for confirmation. Run these steps strictly in order. Pass each tool exactly the values returned by the previous steps; do not fabricate any value. -1. check_product (merchant MCP) — confirm the item is still available at the expected price. -2. checkConstraintsAgainstMandate(open_checkout_mandate_id, current_price, available) — re-verify; only continue if meets_constraints is true and available is true. -3. assemble_cart(item_id, qty) — build the cart. Keep the returned cart_id. -4. create_checkout(cart_id, open_checkout_mandate_id) — get the ES256-signed checkout JWT. Keep checkout_jwt, checkout_jwt_hash, and open_checkout_hash. -5. createCheckoutPresentation(open_checkout_mandate_id, checkout_jwt_hash, cart_id) — build the closed-checkout mandate bound to checkout_jwt_hash. Keep checkout_mandate_chain_id. -6. createPaymentPresentation(open_payment_mandate_id, checkout_jwt_hash) — build the closed-payment mandate; its holder binding commits to checkout_jwt_hash. Keep payment_mandate_chain_id. -7. issue_payment_credential(payment_mandate_chain_id, open_checkout_hash, checkout_jwt_hash) — the credentials provider re-verifies the payment mandate is holder-bound to this exact checkout_jwt_hash before issuing the token. Pass the SAME checkout_jwt_hash and open_checkout_hash from step 4. -8. complete_checkout(checkout_jwt, payment_credential) — hand the credential to the merchant to finalize the order. It returns TWO distinct receipts: \`payment_receipt\` (signed by the PSP) and \`checkout_receipt\` (signed by the merchant). Keep both. -9. Verify the two receipts with the matching verifier — do NOT swap them: - a. verify_payment_receipt(payment_receipt) — pass the \`payment_receipt\` field from step 8 (the PSP-signed one). It is verified against the PSP key. - b. verifyCheckoutReceipt(mandate_chain_id = the checkout_mandate_chain_id from step 5, nonce = checkout_jwt_hash from step 4) — verifies the closed checkout mandate's holder binding. +1. check_product (merchant MCP) — confirm the item is still available at the expected price. Keep item_id and price. +2. checkConstraintsAgainstMandate(current_price, available) — re-verify against the signed L2 mandate; only continue if meets_constraints is true and available is true. +3. assemble_cart(item_id, qty) — build the cart. Keep the returned cart_id and total (minor units / cents). +4. create_checkout(cart_id) — get the ES256-signed merchant checkout JWT. Keep checkout_jwt and checkout_jwt_hash. +5. createMandateFulfillment(checkout_jwt, checkout_jwt_hash, item_id, amount_cents = the cart total from step 3) — the agent builds the split Layer 3 fulfillment (L3a payment for the network + L3b checkout for the merchant) bound to checkout_jwt_hash. Keep checkout_mandate_chain_id and payment_mandate_chain_id. +6. issue_payment_credential(payment_mandate_chain_id) — the credentials provider verifies the payment-side chain (L1 -> L2 -> L3a) and enforces the mandate constraints before issuing the token. Keep payment_token. +7. complete_checkout(checkout_jwt, payment_credential, checkout_mandate_chain_id) — the merchant verifies the checkout-side chain (L1 -> L2 -> L3b), then finalizes the order. It returns TWO distinct receipts: \`payment_receipt\` (signed by the PSP) and \`checkout_receipt\` (signed by the merchant). Keep both. +8. Verify the two receipts with the matching verifier — do NOT swap them: + a. verify_payment_receipt(payment_receipt) — pass the \`payment_receipt\` field from step 7 (the PSP-signed one). It is verified against the PSP key. + b. verifyCheckoutReceipt(checkout_receipt) — pass the \`checkout_receipt\` field from step 7 (the merchant-signed one). Then emit a brief purchase_complete summary (order id + both receipts) as JSON. Principles: -- Mandate integrity: use only data from tools; pass checkout mandates to checkout operations and payment mandates to payment operations. +- Mandate integrity: use only data from tools; the chain ids returned by createMandateFulfillment route the checkout chain to the merchant and the payment chain to the credentials provider. - Idempotency: never repeat a tool that already succeeded. - You do NOT have access to the PSP's initiate_payment tool; settlement is driven by issue_payment_credential + complete_checkout. diff --git a/code/samples/typescript/test/unit/constraints-and-binding.test.ts b/code/samples/typescript/test/unit/constraints-and-binding.test.ts deleted file mode 100644 index 99bc71c4..00000000 --- a/code/samples/typescript/test/unit/constraints-and-binding.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Covers the two fidelity upgrades: - * - constraint checker (amount + merchant allowlist) - * - hash binding: a payment presentation bound to checkout A must NOT verify - * under checkout B's hash (replay protection). - */ - -import { describe, it, expect } from 'vitest'; -import { - checkConstraints, - generateKeyPair, - issueOpenMandate, - presentClosedMandate, - verifyMandate, -} from '../../src/common/sdjwt/index.js'; - -describe('constraint checker', () => { - it('passes when price within cap and merchant allowed', () => { - const r = checkConstraints( - { price: 150, currency: 'USD', merchant: 'acme' }, - { priceCap: 200, currency: 'USD', allowedMerchants: ['acme', 'globex'] }, - ); - expect(r.meetsConstraints).toBe(true); - expect(r.violations).toEqual([]); - }); - - it('fails when price exceeds the cap', () => { - const r = checkConstraints({ price: 250 }, { priceCap: 200 }); - expect(r.meetsConstraints).toBe(false); - expect(r.violations[0]).toContain('exceeds cap'); - }); - - it('fails when merchant not in allowlist', () => { - const r = checkConstraints( - { price: 10, merchant: 'sketchy' }, - { priceCap: 100, allowedMerchants: ['acme'] }, - ); - expect(r.meetsConstraints).toBe(false); - expect(r.violations[0]).toContain('not in allowlist'); - }); - - it('ignores merchant when the mandate does not restrict merchants', () => { - const r = checkConstraints({ price: 10, merchant: 'anyone' }, { priceCap: 100 }); - expect(r.meetsConstraints).toBe(true); - }); -}); - -describe('hash binding (replay protection)', () => { - it('rejects a payment presentation under a different checkout hash', async () => { - const user = await generateKeyPair(); // issuer - const agent = await generateKeyPair(); // holder (cnf) - const checkoutHashA = 'checkout-A-hash'; - const checkoutHashB = 'checkout-B-hash'; - - const openPayment = await issueOpenMandate({ - claims: { kind: 'open_payment', constraint_price_cap: 200 }, - disclosable: [], - issuerPrivateJwk: user.privateKey, - holderPublicJwk: agent.publicKey, - }); - - // Agent presents the payment mandate bound to checkout A. - const closedForA = await presentClosedMandate({ - openMandateSdJwt: openPayment, - disclose: [], - holderPrivateJwk: agent.privateKey, - nonce: checkoutHashA, - audience: 'credentials-provider', - }); - - // Verifying under checkout A's hash succeeds and is key-bound. - const okA = await verifyMandate({ - mandateSdJwt: closedForA, - issuerPublicJwk: user.publicKey, - nonce: checkoutHashA, - }); - expect(okA.keyBound).toBe(true); - - // The same presentation must NOT verify under checkout B's hash. - await expect( - verifyMandate({ mandateSdJwt: closedForA, issuerPublicJwk: user.publicKey, nonce: checkoutHashB }), - ).rejects.toThrow(); - }); -}); diff --git a/code/samples/typescript/test/unit/sdjwt-mandate.test.ts b/code/samples/typescript/test/unit/sdjwt-mandate.test.ts deleted file mode 100644 index 65a43e54..00000000 --- a/code/samples/typescript/test/unit/sdjwt-mandate.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Spike: proves the @sd-jwt/core mandate primitives do real SD-JWT with - * selective disclosure + ES256 + cnf.jwk key binding (KB-SD-JWT) — the - * AP2 v0.2 open->closed delegation hop. Runs without Gemini / any server. - */ - -import { describe, it, expect } from 'vitest'; -import { - generateKeyPair, - issueOpenMandate, - presentClosedMandate, - verifyMandate, -} from '../../src/common/sdjwt/index.js'; - -describe('SD-JWT mandate primitives (spike)', () => { - it('issues an open mandate as a real SD-JWT with selective disclosure', async () => { - const issuer = await generateKeyPair(); // user device key - const holder = await generateKeyPair(); // agent key - - const sdjwt = await issueOpenMandate({ - claims: { - merchant: 'acme-shoes', - amount_cap: 20000, // minor units - currency: 'USD', - item: 'supershoe-gold-9', - }, - disclosable: ['amount_cap', 'item'], - issuerPrivateJwk: issuer.privateKey, - holderPublicJwk: holder.publicKey, - }); - - // Compact SD-JWT form: ~~...~ (tilde separated) - expect(sdjwt.split('~').length).toBeGreaterThan(1); - }); - - it('verifies the issuer signature (no key binding on the bare SD-JWT)', async () => { - const issuer = await generateKeyPair(); - const holder = await generateKeyPair(); - - const sdjwt = await issueOpenMandate({ - claims: { merchant: 'acme-shoes', amount_cap: 20000, item: 'x' }, - disclosable: ['amount_cap', 'item'], - issuerPrivateJwk: issuer.privateKey, - holderPublicJwk: holder.publicKey, - }); - - const result = await verifyMandate({ - mandateSdJwt: sdjwt, - issuerPublicJwk: issuer.publicKey, - }); - - expect(result.payload.merchant).toBe('acme-shoes'); - // cnf must survive (never disclosed) so the next hop can key-bind. - expect((result.payload.cnf as { jwk?: unknown }).jwk).toBeDefined(); - expect(result.keyBound).toBe(false); - }); - - it('presents a closed mandate (KB-SD-JWT) and verifies the key binding', async () => { - const issuer = await generateKeyPair(); - const holder = await generateKeyPair(); - const nonce = 'verifier-nonce-123'; - const audience = 'credentials-provider'; - - const open = await issueOpenMandate({ - claims: { merchant: 'acme-shoes', amount_cap: 20000, item: 'supershoe' }, - disclosable: ['amount_cap', 'item'], - issuerPrivateJwk: issuer.privateKey, - holderPublicJwk: holder.publicKey, - }); - - const closed = await presentClosedMandate({ - openMandateSdJwt: open, - disclose: ['amount_cap'], // reveal only the cap, keep `item` hidden - holderPrivateJwk: holder.privateKey, - nonce, - audience, - }); - - const verified = await verifyMandate({ - mandateSdJwt: closed, - issuerPublicJwk: issuer.publicKey, - nonce, - }); - - expect(verified.keyBound).toBe(true); - // selective disclosure: amount_cap revealed, item withheld - expect(verified.payload.amount_cap).toBe(20000); - expect(verified.payload.item).toBeUndefined(); - expect(verified.payload.merchant).toBe('acme-shoes'); // always-disclosed - }); - - it('rejects key binding signed by the wrong holder key', async () => { - const issuer = await generateKeyPair(); - const holder = await generateKeyPair(); - const attacker = await generateKeyPair(); - - const open = await issueOpenMandate({ - claims: { merchant: 'acme', amount_cap: 100, item: 'y' }, - disclosable: ['amount_cap'], - issuerPrivateJwk: issuer.privateKey, - holderPublicJwk: holder.publicKey, // cnf binds the real holder - }); - - // Attacker presents with their own key instead of the cnf-bound holder key. - const forged = await presentClosedMandate({ - openMandateSdJwt: open, - disclose: ['amount_cap'], - holderPrivateJwk: attacker.privateKey, - nonce: 'n', - audience: 'credentials-provider', - }); - - await expect( - verifyMandate({ mandateSdJwt: forged, issuerPublicJwk: issuer.publicKey, nonce: 'n' }), - ).rejects.toThrow(); - }); -}); diff --git a/code/samples/typescript/test/unit/vi-chain.test.ts b/code/samples/typescript/test/unit/vi-chain.test.ts new file mode 100644 index 00000000..eda01377 --- /dev/null +++ b/code/samples/typescript/test/unit/vi-chain.test.ts @@ -0,0 +1,267 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * End-to-end Verifiable Intent chain tests — the TypeScript replication of the + * official Python reference flows (verifiable_intent/python/examples): + * - autonomous_flow.py: L1 issuer -> L2 user (open, agent delegation) + * -> L3a/L3b agent fulfillment -> merchant + network verification + constraints + * - immediate_flow.py: L1 issuer -> L2 user (final values, no L3) -> verification + * + * Runs entirely in-process (no servers / no Gemini), using @verifiable-intent/core + * via src/common/vi. A fixed `currentTime` keeps verification deterministic. + */ + +import { describe, it, expect } from 'vitest'; +import { + ACCEPTABLE_ITEMS, + MERCHANTS, + PAYMENT_INSTRUMENT, + ROLE_KIDS, + type ViKeyPair, + createAgentFulfillment, + createCheckoutJwt, + checkoutHashFromJwt, + createUserMandateAutonomous, + createUserMandateImmediate, + decodeSdJwt, + findProduct, + generateEs256Key, + issueIssuerCredential, + verifyChain, + verifyCheckoutChain, + verifyPaymentChainAndConstraints, +} from '../../src/common/vi/index.js'; + +async function makeKey(name: string): Promise { + const { publicKey, privateKey } = await generateEs256Key(); + return { publicKey, privateKey, kid: ROLE_KIDS[name] ?? `${name}-key-1` }; +} + +const NOW = 1_900_000_000; // fixed, deterministic timestamp + +describe('Verifiable Intent — autonomous (3-layer) flow', () => { + it('issues L1->L2->L3a/L3b and verifies merchant + network chains with constraints', async () => { + const issuer = await makeKey('issuer'); + const user = await makeKey('user'); + const agent = await makeKey('agent'); + const merchant = await makeKey('merchant'); + + // L1 — Credentials Provider issues the issuer credential, binding the user key. + const l1 = await issueIssuerCredential({ + userPublicJwk: user.publicKey, + issuer, + sub: 'user-alice-001', + iat: NOW, + email: 'alice@example.com', + panLastFour: '1234', + }); + + // L2 — User signs the autonomous mandate, delegating to the agent. + const l2 = await createUserMandateAutonomous({ + l1Serialized: l1, + user, + agentPublicJwk: agent.publicKey, + agentKid: agent.kid, + promptSummary: 'Buy a Babolat tennis racket under $400', + iat: NOW, + merchants: MERCHANTS, + acceptableItems: ACCEPTABLE_ITEMS, + paymentInstrument: PAYMENT_INSTRUMENT, + amountMin: 10000, + amountMax: 40000, + recurrence: { frequency: 'YEAR', startDate: '2026-01-01', endDate: '2028-01-01', number: 3 }, + }); + + // Merchant produces a checkout JWT for the agent-selected racket. + const racket = findProduct('BAB86345')!; + const checkoutJwt = await createCheckoutJwt([{ sku: racket.sku, quantity: 1 }], merchant); + const checkoutHash = checkoutHashFromJwt(checkoutJwt); + + // L3 — Agent builds split fulfillment + per-recipient L2 presentations. + const fulfillment = await createAgentFulfillment({ + l2Serialized: l2, + agent, + checkoutJwt, + checkoutHash, + payee: MERCHANTS[0], + itemId: racket.sku, + amount: racket.price, + paymentInstrument: PAYMENT_INSTRUMENT, + iat: NOW, + }); + + // Merchant verifies the checkout-side chain (full L2 for pairing, as the example does). + const merchantResult = await verifyCheckoutChain({ + l1Serialized: l1, + l2CheckoutSerialized: fulfillment.l2CheckoutSerialized, + l3CheckoutSerialized: fulfillment.l3CheckoutSerialized, + issuerPublicJwk: issuer.publicKey, + l2Serialized: l2, + currentTime: NOW, + }); + expect(merchantResult.valid).toBe(true); + expect(merchantResult.errors).toEqual([]); + expect(merchantResult.l2CheckoutDisclosed).toBe(true); + + // Network verifies the payment-side chain and enforces constraints (STRICT). + const networkOutcome = await verifyPaymentChainAndConstraints({ + l1Serialized: l1, + l2PaymentSerialized: fulfillment.l2PaymentSerialized, + l3PaymentSerialized: fulfillment.l3PaymentSerialized, + issuerPublicJwk: issuer.publicKey, + l2Serialized: l2, + currentTime: NOW, + }); + expect(networkOutcome.result.valid).toBe(true); + expect(networkOutcome.constraints).not.toBeNull(); + expect(networkOutcome.constraints!.satisfied).toBe(true); + expect(networkOutcome.valid).toBe(true); + }); + + it('verifies the merchant chain without the full L2 (privacy-strict: checkout presentation only)', async () => { + const issuer = await makeKey('issuer'); + const user = await makeKey('user'); + const agent = await makeKey('agent'); + const merchant = await makeKey('merchant'); + + const l1 = await issueIssuerCredential({ userPublicJwk: user.publicKey, issuer, sub: 'u', iat: NOW }); + const l2 = await createUserMandateAutonomous({ + l1Serialized: l1, + user, + agentPublicJwk: agent.publicKey, + agentKid: agent.kid, + promptSummary: 'racket', + iat: NOW, + merchants: MERCHANTS, + acceptableItems: ACCEPTABLE_ITEMS, + paymentInstrument: PAYMENT_INSTRUMENT, + amountMin: 10000, + amountMax: 40000, + }); + const racket = findProduct('BAB86345')!; + const checkoutJwt = await createCheckoutJwt([{ sku: racket.sku }], merchant); + const checkoutHash = checkoutHashFromJwt(checkoutJwt); + const fulfillment = await createAgentFulfillment({ + l2Serialized: l2, + agent, + checkoutJwt, + checkoutHash, + payee: MERCHANTS[0], + itemId: racket.sku, + amount: racket.price, + paymentInstrument: PAYMENT_INSTRUMENT, + iat: NOW, + }); + + // No l2Serialized → defaults to the checkout presentation the merchant actually receives. + const merchantResult = await verifyCheckoutChain({ + l1Serialized: l1, + l2CheckoutSerialized: fulfillment.l2CheckoutSerialized, + l3CheckoutSerialized: fulfillment.l3CheckoutSerialized, + issuerPublicJwk: issuer.publicKey, + currentTime: NOW, + }); + expect(merchantResult.valid).toBe(true); + }); + + it('rejects an over-budget payment via STRICT constraint enforcement', async () => { + const issuer = await makeKey('issuer'); + const user = await makeKey('user'); + const agent = await makeKey('agent'); + const merchant = await makeKey('merchant'); + + const l1 = await issueIssuerCredential({ userPublicJwk: user.publicKey, issuer, sub: 'u', iat: NOW }); + const l2 = await createUserMandateAutonomous({ + l1Serialized: l1, + user, + agentPublicJwk: agent.publicKey, + agentKid: agent.kid, + promptSummary: 'cheap racket only', + iat: NOW, + merchants: MERCHANTS, + acceptableItems: ACCEPTABLE_ITEMS, + paymentInstrument: PAYMENT_INSTRUMENT, + amountMin: 1000, + amountMax: 20000, // cap BELOW the racket price (27999) + }); + const racket = findProduct('BAB86345')!; + const checkoutJwt = await createCheckoutJwt([{ sku: racket.sku }], merchant); + const checkoutHash = checkoutHashFromJwt(checkoutJwt); + const fulfillment = await createAgentFulfillment({ + l2Serialized: l2, + agent, + checkoutJwt, + checkoutHash, + payee: MERCHANTS[0], + itemId: racket.sku, + amount: racket.price, // 27999 > 20000 cap + paymentInstrument: PAYMENT_INSTRUMENT, + iat: NOW, + }); + + const networkOutcome = await verifyPaymentChainAndConstraints({ + l1Serialized: l1, + l2PaymentSerialized: fulfillment.l2PaymentSerialized, + l3PaymentSerialized: fulfillment.l3PaymentSerialized, + issuerPublicJwk: issuer.publicKey, + l2Serialized: l2, + currentTime: NOW, + }); + // Chain is cryptographically valid, but the amount violates the mandate. + expect(networkOutcome.result.valid).toBe(true); + expect(networkOutcome.constraints!.satisfied).toBe(false); + expect(networkOutcome.valid).toBe(false); + expect(networkOutcome.errors.length).toBeGreaterThan(0); + }); +}); + +describe('Verifiable Intent — immediate (2-layer) flow', () => { + it('issues L1->L2 with final values and verifies the 2-layer chain', async () => { + const issuer = await makeKey('issuer'); + const user = await makeKey('user'); + const merchant = await makeKey('merchant'); + + const l1 = await issueIssuerCredential({ + userPublicJwk: user.publicKey, + issuer, + sub: 'user-bob-001', + iat: NOW, + email: 'bob@example.com', + panLastFour: '5678', + }); + + const checkoutJwt = await createCheckoutJwt([{ sku: 'BAB86345', quantity: 1 }], merchant); + const l2 = await createUserMandateImmediate({ + l1Serialized: l1, + user, + checkoutJwt, + paymentInstrument: PAYMENT_INSTRUMENT, + payee: MERCHANTS[0], + amount: 27999, + iat: NOW, + promptSummary: 'Purchase Babolat Pure Aero racket', + }); + + // Merchant: structural check (issuer signature verified out of band). + const merchantResult = await verifyChain(decodeSdJwt(l1), decodeSdJwt(l2), { + skipIssuerVerification: true, + currentTime: NOW, + }); + expect(merchantResult.valid).toBe(true); + + // Network: full verification with the issuer key. + const networkResult = await verifyChain(decodeSdJwt(l1), decodeSdJwt(l2), { + issuerPublicJwk: issuer.publicKey, + l1Serialized: l1, + currentTime: NOW, + }); + expect(networkResult.valid).toBe(true); + expect(networkResult.errors).toEqual([]); + }); +}); From a1240517af1cb7672711d5b49dcd64a32e74f4e9 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Mon, 1 Jun 2026 17:39:54 -0500 Subject: [PATCH 06/17] chore(typescript): track adk web entry-point (src/web-agents) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Start tracking the `adk web src/web-agents` entry-point that the `dev` script launches: a dedicated agents directory that re-exports the shopping-agent so ADK lists the app under its folder name (pointing adk web at src/roles is not viable — the *-mcp stdio servers and shopping-agent-v2 collide in ADK's loader). The file was previously untracked; without it a fresh checkout's `npm run dev` fails to start the web UI. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/web-agents/shopping-agent/agent.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 code/samples/typescript/src/web-agents/shopping-agent/agent.ts diff --git a/code/samples/typescript/src/web-agents/shopping-agent/agent.ts b/code/samples/typescript/src/web-agents/shopping-agent/agent.ts new file mode 100644 index 00000000..33f71bd2 --- /dev/null +++ b/code/samples/typescript/src/web-agents/shopping-agent/agent.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * ADK web entry: a dedicated agents directory so `adk web src/web-agents` + * lists the app under the folder name ("shopping-agent") instead of the + * generic "agent" you get when pointing adk web directly at an agent folder. + * Pointing adk web at src/roles is not viable — its heterogeneous siblings + * (the *-mcp stdio servers and shopping-agent-v2) collide in ADK's loader. + */ + +export { rootAgent, shoppingAgent } from '../../roles/shopping-agent/agent.js'; From ba62ff4ca18b42e23a6672c50fcdf20cc47ed818 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Mon, 1 Jun 2026 17:56:28 -0500 Subject: [PATCH 07/17] chore(typescript): add autonomous improve+test loop harness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A headless, git-checkpointed loop that keeps hardening + testing the Verifiable Intent integration, one verified backlog item at a time. - improve-loop.prompt.md: stable per-iteration prompt (pick one BACKLOG item -> small change -> typecheck+lint+`vitest run test/unit` must pass -> commit; never weaken tests; revert if not green; e2e excluded). - BACKLOG.md: the work queue (coverage, negative-path tests, server-free MCP tests, aud/nonce pinning, README). - improve-loop.sh: the runner — `--bare`, `--permission-mode acceptEdits`, a scoped tool allowlist (npm/npx/git/node + file tools only), an iteration cap, and a stop-when-green-and-backlog-empty gate. - test/unit/vi-chain-negative.test.ts: seed rejection tests (expired L3, missing/wrong issuer key, out-of-mandate payee). Unit suite now 15/15. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 22 +++ .../samples/typescript/improve-loop.prompt.md | 35 +++++ code/samples/typescript/improve-loop.sh | 61 ++++++++ .../test/unit/vi-chain-negative.test.ts | 140 ++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 code/samples/typescript/BACKLOG.md create mode 100644 code/samples/typescript/improve-loop.prompt.md create mode 100755 code/samples/typescript/improve-loop.sh create mode 100644 code/samples/typescript/test/unit/vi-chain-negative.test.ts diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md new file mode 100644 index 00000000..d303f1fe --- /dev/null +++ b/code/samples/typescript/BACKLOG.md @@ -0,0 +1,22 @@ +# Verifiable Intent TS integration — improvement backlog + +`improve-loop.sh` works ONE unchecked item at a time. Keep items small and +independently verifiable (`npx tsc --noEmit` + `npm run lint` + +`npx vitest run test/unit` all green). Check items off (`- [x]`) when done. + +## Test coverage & hardening +- [ ] Enable coverage: `npm i -D @vitest/coverage-v8` (not yet installed); add a unit-only `"test:coverage:unit": "vitest run test/unit --coverage"` script (the existing `test:coverage` also runs e2e) + a coverage block in `vitest.config.ts`; report coverage for `src/common/vi`. +- [ ] Strengthen `src/common/vi` negative-path tests (see `test/unit/vi-chain-negative.test.ts`): add a tampered-L2 case (mutate a disclosed claim → chain invalid) and a kid-mismatch case (L3 signed by a key whose kid ≠ L2 `cnf.kid` → invalid). +- [ ] Add immediate-flow rejection tests: bad issuer signature, and an L2 whose `sd_hash` does not match L1 → `verifyChain` invalid. +- [ ] Constraint-checker edge cases: amount exactly at the cap (allowed), one cent over (rejected), currency mismatch, empty `acceptable_items` (wildcard). + +## Make the MCP role logic testable without servers +- [ ] Extract the pure logic of merchant-agent-mcp `complete_checkout`, credentials-provider-mcp `issue_payment_credential`, and merchant `create_checkout` into small exported functions (no `McpServer`/stdio), and unit-test their happy + error paths against the persisted-file contract in a temp `TEMP_DB`. +- [ ] Add a deterministic cross-role integration test: `assembleAndSignMandates` → `create_checkout` → `createMandateFulfillment` → `verifyCheckoutChain` + `verifyPaymentChainAndConstraints`, all via the file contract, asserting a valid end-to-end purchase and an over-budget rejection. No Gemini. + +## Robustness +- [ ] Pin `expectedL3*Aud` / `expectedL3*Nonce` (and `expectedL2*`) in the role `verifyChain` calls (currently optional) so a presentation for a different audience/transaction is rejected; add tests proving it. +- [ ] `verifyPaymentChainAndConstraints`: surface a clear error when the issuer key is missing, and test it. + +## Docs +- [ ] Add `src/common/vi/README.md` documenting the L1→L2→L3 flow (diagram, field reference, who-stores-what), matching the integration. diff --git a/code/samples/typescript/improve-loop.prompt.md b/code/samples/typescript/improve-loop.prompt.md new file mode 100644 index 00000000..be22caa1 --- /dev/null +++ b/code/samples/typescript/improve-loop.prompt.md @@ -0,0 +1,35 @@ +# Autonomous improve + test loop — Verifiable Intent TypeScript integration + +You are running headless inside `code/samples/typescript` (the AP2 TypeScript +sample). Your job: make ONE small, fully-verified improvement per run that +hardens or tests the Verifiable Intent integration (`src/common/vi` and the role +wiring in `src/roles`). Then stop. + +## Procedure (do this exactly, once) + +1. Read `BACKLOG.md`. Choose the single highest-priority unchecked item `- [ ]`. + If every item is already checked, pick the most valuable NEW hardening/test + task you can find and add it. +2. Make a small, focused change. Strongly prefer ADDING or STRENGTHENING tests + over changing production code. If you change production code, it must be to + fix a real bug that a test now proves. +3. Verify — ALL of these must pass before you keep the change: + - `npx tsc --noEmit` (this sample has no `typecheck` script) + - `npm run lint` + - `npx vitest run test/unit` + Do NOT run `npm test` or anything in `test/e2e/` — e2e needs a live Gemini + API key and running servers, and will fail or hang here. +4. If green: tick the item in `BACKLOG.md` (`- [x]`), then `git add -A` and + `git commit -m "loop: "`. If you discovered new useful work, + append new `- [ ]` items to `BACKLOG.md`. +5. If you cannot get to green: run `git checkout -- .` to revert your changes, + leave the item unchecked, and append a short `blocked:` note under it. + +## Hard rules + +- Never delete, skip, weaken, or loosen an assertion just to make tests pass. +- Never edit files under `.git/`, `.github/`, `.claude/`, or the npm-linked + `node_modules/@verifiable-intent` package. Only touch this sample. +- Keep each change minimal and reversible. One backlog item per run, then stop. +- Amounts are in minor units (cents). Scenario fixtures live in + `src/common/vi/fixtures.ts`; the layer model lives in `src/common/vi/flow.ts`. diff --git a/code/samples/typescript/improve-loop.sh b/code/samples/typescript/improve-loop.sh new file mode 100755 index 00000000..a1a434da --- /dev/null +++ b/code/samples/typescript/improve-loop.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# +# Autonomous improve + test loop for the Verifiable Intent TS integration. +# +# Each iteration runs Claude Code headless against improve-loop.prompt.md, which +# completes ONE backlog item, verifies it (typecheck + lint + unit tests), and +# commits it. The loop stops when all gates are green AND BACKLOG.md has no +# unchecked items, or after MAX_ITERS iterations. +# +# Guardrails: --bare (no hooks/plugins), acceptEdits, a scoped tool allowlist +# (only npm/npx/git/node + file tools — no network), and a git commit per +# iteration so any step is one `git reset` away from rollback. e2e tests are +# never run here (they need a Gemini key + live servers). +# +# Usage: ./improve-loop.sh [max_iterations] # default 12 +# LOOP_MODEL=opus ./improve-loop.sh 5 # override model +# +set -uo pipefail +cd "$(dirname "$0")" + +MAX_ITERS="${1:-12}" +MODEL="${LOOP_MODEL:-sonnet}" +PROMPT_FILE="improve-loop.prompt.md" +ALLOWED="Read,Edit,Write,Glob,Grep,Bash(npm:*),Bash(npx:*),Bash(git:*),Bash(node:*)" + +if [ ! -f "$PROMPT_FILE" ]; then + echo "error: $PROMPT_FILE not found (run from code/samples/typescript)" >&2 + exit 1 +fi + +gates_green() { + npx tsc --noEmit >/dev/null 2>&1 \ + && npm run lint >/dev/null 2>&1 \ + && npx vitest run test/unit >/dev/null 2>&1 +} + +# No unchecked "- [ ]" items remain in the backlog. +backlog_done() { + ! grep -qE '^[[:space:]]*-[[:space:]]\[ \]' BACKLOG.md +} + +for i in $(seq 1 "$MAX_ITERS"); do + echo "──────────────────────────────────────────────────────────" + echo " iteration $i / $MAX_ITERS" + echo "──────────────────────────────────────────────────────────" + + claude --bare -p "$(cat "$PROMPT_FILE")" \ + --model "$MODEL" \ + --permission-mode acceptEdits \ + --allowedTools "$ALLOWED" + + if gates_green && backlog_done; then + echo "✅ all gates green and BACKLOG.md fully checked — stopping." + break + fi + echo "… not done yet; continuing to next iteration." +done + +echo "" +echo "Loop finished. Recent commits:" +git --no-pager log --oneline -"$MAX_ITERS" 2>/dev/null || true diff --git a/code/samples/typescript/test/unit/vi-chain-negative.test.ts b/code/samples/typescript/test/unit/vi-chain-negative.test.ts new file mode 100644 index 00000000..6f831e12 --- /dev/null +++ b/code/samples/typescript/test/unit/vi-chain-negative.test.ts @@ -0,0 +1,140 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Rejection-path tests for the Verifiable Intent chain — the security + * properties that MUST hold: expired credentials, missing/wrong issuer key, and + * delegation to an out-of-mandate payee are all refused. Seed batch for the + * autonomous improvement loop (see BACKLOG.md). Runs in-process, no servers. + */ + +import { describe, it, expect } from 'vitest'; +import { + ACCEPTABLE_ITEMS, + MERCHANTS, + PAYMENT_INSTRUMENT, + ROLE_KIDS, + type ViKeyPair, + createAgentFulfillment, + createCheckoutJwt, + checkoutHashFromJwt, + createUserMandateAutonomous, + decodeSdJwt, + findProduct, + generateEs256Key, + issueIssuerCredential, + verifyChain, + verifyPaymentChainAndConstraints, +} from '../../src/common/vi/index.js'; + +async function makeKey(name: string): Promise { + const { publicKey, privateKey } = await generateEs256Key(); + return { publicKey, privateKey, kid: ROLE_KIDS[name] ?? `${name}-key-1` }; +} + +const NOW = 1_900_000_000; + +/** Build a full, valid autonomous fulfillment for the tennis-racket scenario. */ +async function buildChain() { + const issuer = await makeKey('issuer'); + const user = await makeKey('user'); + const agent = await makeKey('agent'); + const merchant = await makeKey('merchant'); + + const l1 = await issueIssuerCredential({ userPublicJwk: user.publicKey, issuer, sub: 'u', iat: NOW }); + const l2 = await createUserMandateAutonomous({ + l1Serialized: l1, + user, + agentPublicJwk: agent.publicKey, + agentKid: agent.kid, + promptSummary: 'racket', + iat: NOW, + merchants: MERCHANTS, + acceptableItems: ACCEPTABLE_ITEMS, + paymentInstrument: PAYMENT_INSTRUMENT, + amountMin: 0, + amountMax: 40000, + }); + const racket = findProduct('BAB86345')!; + const checkoutJwt = await createCheckoutJwt([{ sku: racket.sku }], merchant); + const checkoutHash = checkoutHashFromJwt(checkoutJwt); + const fulfillment = await createAgentFulfillment({ + l2Serialized: l2, + agent, + checkoutJwt, + checkoutHash, + payee: MERCHANTS[0], + itemId: racket.sku, + amount: racket.price, + paymentInstrument: PAYMENT_INSTRUMENT, + iat: NOW, + }); + return { issuer, user, agent, merchant, l1, l2, checkoutJwt, checkoutHash, fulfillment, racket }; +} + +describe('Verifiable Intent — rejection paths', () => { + it('rejects an expired L3 (verified well after its exp)', async () => { + const { issuer, l1, fulfillment } = await buildChain(); + // L3 exp = iat + 300; verify ~1h later → expired. + const outcome = await verifyPaymentChainAndConstraints({ + l1Serialized: l1, + l2PaymentSerialized: fulfillment.l2PaymentSerialized, + l3PaymentSerialized: fulfillment.l3PaymentSerialized, + issuerPublicJwk: issuer.publicKey, + currentTime: NOW + 3600, + }); + expect(outcome.valid).toBe(false); + expect(outcome.result.valid).toBe(false); + }); + + it('fails closed when no issuer key is provided and verification is not skipped', async () => { + const { l1, fulfillment } = await buildChain(); + const result = await verifyChain( + decodeSdJwt(l1), + decodeSdJwt(fulfillment.l2PaymentSerialized), + { + l3Payment: decodeSdJwt(fulfillment.l3PaymentSerialized), + l1Serialized: l1, + l2PaymentSerialized: fulfillment.l2PaymentSerialized, + currentTime: NOW, + // intentionally: no issuerPublicJwk, no skipIssuerVerification + }, + ); + expect(result.valid).toBe(false); + }); + + it('rejects a chain verified against the wrong issuer key', async () => { + const { l1, fulfillment } = await buildChain(); + const wrongIssuer = await makeKey('issuer'); + const outcome = await verifyPaymentChainAndConstraints({ + l1Serialized: l1, + l2PaymentSerialized: fulfillment.l2PaymentSerialized, + l3PaymentSerialized: fulfillment.l3PaymentSerialized, + issuerPublicJwk: wrongIssuer.publicKey, + currentTime: NOW, + }); + expect(outcome.valid).toBe(false); + }); + + it('refuses to build a fulfillment for a payee outside the mandate', async () => { + const { agent, l2, checkoutJwt, checkoutHash, racket } = await buildChain(); + await expect( + createAgentFulfillment({ + l2Serialized: l2, + agent, + checkoutJwt, + checkoutHash, + payee: { id: 'merchant-unknown', name: 'Not Allowed', website: 'https://nope.example' }, + itemId: racket.sku, + amount: racket.price, + paymentInstrument: PAYMENT_INSTRUMENT, + iat: NOW, + }), + ).rejects.toThrow(); + }); +}); From f4500971fe3f222e4573aa3a8480a199dbc2c430 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 6 Jun 2026 21:04:36 -0500 Subject: [PATCH 08/17] feat(typescript): pin L3 audience in role verifiers (reject misaddressed mandates) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gap found by the improve loop: complete_checkout and issue_payment_credential called verifyChain without pinning the L3 audience, so the VI library recorded the aud check as "skipped" — a mandate addressed to a different party would pass. - Add shared MERCHANT_AUD / NETWORK_AUD constants in common/vi/fixtures.ts. - Agent stamps them as the L3a/L3b `aud` (flow.ts createAgentFulfillment). - merchant-agent-mcp complete_checkout pins expectedL3CheckoutAud = MERCHANT_AUD. - credentials-provider-mcp issue_payment_credential pins expectedL3PaymentAud = NETWORK_AUD. - Test proves a wrong-audience checkout presentation is rejected. tsc + lint clean; unit suite 16/16. Logged a follow-up replay-protection finding. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 3 ++- .../typescript/src/common/vi/fixtures.ts | 8 ++++++ code/samples/typescript/src/common/vi/flow.ts | 5 ++-- .../roles/credentials-provider-mcp/server.ts | 3 ++- .../src/roles/merchant-agent-mcp/server.ts | 3 ++- .../test/unit/vi-chain-negative.test.ts | 25 +++++++++++++++++++ 6 files changed, 42 insertions(+), 5 deletions(-) diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md index d303f1fe..6184d1aa 100644 --- a/code/samples/typescript/BACKLOG.md +++ b/code/samples/typescript/BACKLOG.md @@ -15,7 +15,8 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + - [ ] Add a deterministic cross-role integration test: `assembleAndSignMandates` → `create_checkout` → `createMandateFulfillment` → `verifyCheckoutChain` + `verifyPaymentChainAndConstraints`, all via the file contract, asserting a valid end-to-end purchase and an over-budget rejection. No Gemini. ## Robustness -- [ ] Pin `expectedL3*Aud` / `expectedL3*Nonce` (and `expectedL2*`) in the role `verifyChain` calls (currently optional) so a presentation for a different audience/transaction is rejected; add tests proving it. +- [x] Pin `expectedL3*Aud` in the role `verifyChain` calls so a presentation addressed to a different party is rejected — merchant pins `MERCHANT_AUD`, CP pins `NETWORK_AUD`, agent stamps both via shared fixtures constants. Test in `test/unit/vi-chain-negative.test.ts`. +- [ ] Replay protection: verifiers don't pin nonce/`transaction_id`, and used payment tokens / mandates aren't deduped — the same authorization could be replayed. Track spent nonces/tokens across the role servers (the VI lib leaves replay to the caller) + add a test. - [ ] `verifyPaymentChainAndConstraints`: surface a clear error when the issuer key is missing, and test it. ## Docs diff --git a/code/samples/typescript/src/common/vi/fixtures.ts b/code/samples/typescript/src/common/vi/fixtures.ts index 0e9a4221..845ee698 100644 --- a/code/samples/typescript/src/common/vi/fixtures.ts +++ b/code/samples/typescript/src/common/vi/fixtures.ts @@ -76,6 +76,14 @@ export const PAYMENT_INSTRUMENT: Dict = { description: 'Mastercard **** 1234', }; +/** + * Stable audiences for the Layer 3 presentations. The agent stamps these as the + * `aud` when minting L3a/L3b, and each verifier pins the matching value so a + * presentation addressed to a different party is rejected (RFC 7519 `aud`). + */ +export const MERCHANT_AUD = 'https://tennis-warehouse.com'; +export const NETWORK_AUD = 'https://www.mastercard.com'; + export function getCatalog(): Product[] { return PRODUCTS; } diff --git a/code/samples/typescript/src/common/vi/flow.ts b/code/samples/typescript/src/common/vi/flow.ts index 81086a56..6ad07445 100644 --- a/code/samples/typescript/src/common/vi/flow.ts +++ b/code/samples/typescript/src/common/vi/flow.ts @@ -61,6 +61,7 @@ import { } from '@verifiable-intent/core'; import { checkoutHashFromJwt } from './checkout-jwt.js'; +import { MERCHANT_AUD, NETWORK_AUD } from './fixtures.js'; import type { ViKeyPair } from './keys.js'; const nowSeconds = (): number => Math.floor(Date.now() / 1000); @@ -319,7 +320,7 @@ export async function createAgentFulfillment(params: AgentFulfillmentParams): Pr // L3a — payment, for the network. const l3aMandate = new PaymentL3Mandate({ nonce, - aud: params.networkAud ?? 'https://www.mastercard.com', + aud: params.networkAud ?? NETWORK_AUD, iat, iss: 'https://agent.example.com', exp, @@ -338,7 +339,7 @@ export async function createAgentFulfillment(params: AgentFulfillmentParams): Pr // L3b — checkout, for the merchant. const l3bMandate = new CheckoutL3Mandate({ nonce, - aud: params.merchantAud ?? 'https://tennis-warehouse.com', + aud: params.merchantAud ?? MERCHANT_AUD, iat, iss: 'https://agent.example.com', exp, diff --git a/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts b/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts index 6dace1f3..709210e1 100644 --- a/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts +++ b/code/samples/typescript/src/roles/credentials-provider-mcp/server.ts @@ -27,7 +27,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { z } from 'zod'; import { verifyJwtEs256, loadPublicJwk } from '../../common/sdjwt/index.js'; -import { loadViPublicJwk, verifyPaymentChainAndConstraints } from '../../common/vi/index.js'; +import { NETWORK_AUD, loadViPublicJwk, verifyPaymentChainAndConstraints } from '../../common/vi/index.js'; const TEMP_DB = process.env.TEMP_DB_DIR ?? '.temp-db'; @@ -89,6 +89,7 @@ server.registerTool( l2PaymentSerialized: l2Payment, l3PaymentSerialized: l3Payment, issuerPublicJwk, + expectedL3PaymentAud: NETWORK_AUD, }); if (!outcome.valid) { const error = { error: 'payment_chain_invalid', message: outcome.errors.join('; ') }; diff --git a/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts b/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts index e54553f3..607d7dba 100644 --- a/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts +++ b/code/samples/typescript/src/roles/merchant-agent-mcp/server.ts @@ -30,7 +30,7 @@ import { loadOrCreateKeyPair, loadPublicJwk, } from '../../common/sdjwt/index.js'; -import { loadViPublicJwk, verifyCheckoutChain } from '../../common/vi/index.js'; +import { MERCHANT_AUD, loadViPublicJwk, verifyCheckoutChain } from '../../common/vi/index.js'; /** Base64url SHA-256 — the checkout JWT hash binds the payment mandate. */ function sha256Base64Url(input: string): string { @@ -378,6 +378,7 @@ server.registerTool( l2CheckoutSerialized: l2Checkout, l3CheckoutSerialized: l3Checkout, issuerPublicJwk: issuerPub, + expectedL3CheckoutAud: MERCHANT_AUD, }); if (!chain.valid) { const error = { error: 'checkout_chain_invalid', message: chain.errors.join('; ') }; diff --git a/code/samples/typescript/test/unit/vi-chain-negative.test.ts b/code/samples/typescript/test/unit/vi-chain-negative.test.ts index 6f831e12..1904c8e6 100644 --- a/code/samples/typescript/test/unit/vi-chain-negative.test.ts +++ b/code/samples/typescript/test/unit/vi-chain-negative.test.ts @@ -16,6 +16,7 @@ import { describe, it, expect } from 'vitest'; import { ACCEPTABLE_ITEMS, + MERCHANT_AUD, MERCHANTS, PAYMENT_INSTRUMENT, ROLE_KIDS, @@ -29,6 +30,7 @@ import { generateEs256Key, issueIssuerCredential, verifyChain, + verifyCheckoutChain, verifyPaymentChainAndConstraints, } from '../../src/common/vi/index.js'; @@ -121,6 +123,29 @@ describe('Verifiable Intent — rejection paths', () => { expect(outcome.valid).toBe(false); }); + it('rejects a checkout presentation addressed to a different audience', async () => { + const { issuer, l1, fulfillment } = await buildChain(); // L3b aud defaults to MERCHANT_AUD + const good = await verifyCheckoutChain({ + l1Serialized: l1, + l2CheckoutSerialized: fulfillment.l2CheckoutSerialized, + l3CheckoutSerialized: fulfillment.l3CheckoutSerialized, + issuerPublicJwk: issuer.publicKey, + currentTime: NOW, + expectedL3CheckoutAud: MERCHANT_AUD, + }); + expect(good.valid).toBe(true); + + const wrong = await verifyCheckoutChain({ + l1Serialized: l1, + l2CheckoutSerialized: fulfillment.l2CheckoutSerialized, + l3CheckoutSerialized: fulfillment.l3CheckoutSerialized, + issuerPublicJwk: issuer.publicKey, + currentTime: NOW, + expectedL3CheckoutAud: 'https://evil.example', + }); + expect(wrong.valid).toBe(false); + }); + it('refuses to build a fulfillment for a payee outside the mandate', async () => { const { agent, l2, checkoutJwt, checkoutHash, racket } = await buildChain(); await expect( From 8326dbe8e05c56e67b92436be4ff126e4cfacdd6 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 6 Jun 2026 21:08:24 -0500 Subject: [PATCH 09/17] test(typescript): add cross-role file-contract integration test Locks the wiring the MCP servers rely on, without spawning servers or Gemini: the agent writes l1/l2/chk_*/pay_* to a temp dir exactly as shopping-agent-v2 does, then merchant (verifyCheckoutChain) and network (verifyPaymentChainAnd- Constraints) read them back and verify with the iteration-1 aud pinning. Covers a valid end-to-end purchase and an over-budget rejection. Also logged two gaps the audit surfaced (BACKLOG.md): (1) merchant slug item-ids vs L2 acceptable_items skus can mismatch and break createAgentFulfillment; (2) the minted pay_token isn't bound to checkout_jwt_hash. tsc + lint clean; unit suite 18/18. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 4 +- .../test/unit/vi-integration.test.ts | 158 ++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 code/samples/typescript/test/unit/vi-integration.test.ts diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md index 6184d1aa..835e469c 100644 --- a/code/samples/typescript/BACKLOG.md +++ b/code/samples/typescript/BACKLOG.md @@ -12,11 +12,13 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + ## Make the MCP role logic testable without servers - [ ] Extract the pure logic of merchant-agent-mcp `complete_checkout`, credentials-provider-mcp `issue_payment_credential`, and merchant `create_checkout` into small exported functions (no `McpServer`/stdio), and unit-test their happy + error paths against the persisted-file contract in a temp `TEMP_DB`. -- [ ] Add a deterministic cross-role integration test: `assembleAndSignMandates` → `create_checkout` → `createMandateFulfillment` → `verifyCheckoutChain` + `verifyPaymentChainAndConstraints`, all via the file contract, asserting a valid end-to-end purchase and an over-budget rejection. No Gemini. +- [x] Add a deterministic cross-role integration test via the persisted-file contract (`test/unit/vi-integration.test.ts`): agent writes `l1/l2/chk_*/pay_*` to a temp dir, merchant + network read them back and verify with aud pinning; asserts a valid purchase and an over-budget rejection. No Gemini/servers. (Tool-level tests of the actual `*.execute` handlers are still worth adding — see item above.) +- [ ] **Gap (flow):** the merchant MCP serves generated slug item ids (`_0` from `generateInventoryEntry`), but the L2 `acceptable_items` default to the VI catalog skus (BAB86345). If consent signs L2 before `search_inventory` supplies the merchant item id, `createAgentFulfillment` can't match the item disclosure and throws. Fix: have the merchant serve the VI catalog (sku ids), or require consent to thread the merchant `item_id` into the mandate. ## Robustness - [x] Pin `expectedL3*Aud` in the role `verifyChain` calls so a presentation addressed to a different party is rejected — merchant pins `MERCHANT_AUD`, CP pins `NETWORK_AUD`, agent stamps both via shared fixtures constants. Test in `test/unit/vi-chain-negative.test.ts`. - [ ] Replay protection: verifiers don't pin nonce/`transaction_id`, and used payment tokens / mandates aren't deduped — the same authorization could be replayed. Track spent nonces/tokens across the role servers (the VI lib leaves replay to the caller) + add a test. +- [ ] **Gap (binding):** credentials-provider mints `pay_token_*` after verifying the payment chain, but the token isn't cryptographically bound to `checkout_jwt_hash`, and `complete_checkout` / the PSP don't re-verify the chain — a minted token could be presented for a different settlement. Bind the token to the checkout hash (and/or have the PSP re-verify) + add a test. - [ ] `verifyPaymentChainAndConstraints`: surface a clear error when the issuer key is missing, and test it. ## Docs diff --git a/code/samples/typescript/test/unit/vi-integration.test.ts b/code/samples/typescript/test/unit/vi-integration.test.ts new file mode 100644 index 00000000..60f18552 --- /dev/null +++ b/code/samples/typescript/test/unit/vi-integration.test.ts @@ -0,0 +1,158 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Cross-role integration test — exercises the exact persisted-file contract the + * MCP servers use (the serialized SD-JWTs written by shopping-agent-v2 and read + * back by merchant-agent-mcp / credentials-provider-mcp), with the same + * aud-pinned verification each role performs. This locks the wiring end-to-end + * without spawning servers or calling Gemini. + * + * File contract (TEMP_DB): + * l1.sdjwt, l2.sdjwt (agent: assembleAndSignMandates) + * .sdjwt, .l2.sdjwt (agent: createMandateFulfillment — merchant view) + * .sdjwt, .l2.sdjwt (agent: createMandateFulfillment — network view) + */ + +import { afterEach, beforeEach, describe, it, expect } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { + ACCEPTABLE_ITEMS, + MERCHANT_AUD, + MERCHANTS, + NETWORK_AUD, + PAYMENT_INSTRUMENT, + ROLE_KIDS, + type ViKeyPair, + createAgentFulfillment, + createCheckoutJwt, + checkoutHashFromJwt, + createUserMandateAutonomous, + findProduct, + generateEs256Key, + issueIssuerCredential, + verifyCheckoutChain, + verifyPaymentChainAndConstraints, +} from '../../src/common/vi/index.js'; + +const NOW = 1_900_000_000; + +async function makeKey(name: string): Promise { + const { publicKey, privateKey } = await generateEs256Key(); + return { publicKey, privateKey, kid: ROLE_KIDS[name] ?? `${name}-key-1` }; +} + +let TEMP_DB: string; +beforeEach(() => { + TEMP_DB = fs.mkdtempSync(path.join(os.tmpdir(), 'vi-it-')); +}); +afterEach(() => { + fs.rmSync(TEMP_DB, { recursive: true, force: true }); +}); + +const persist = (name: string, content: string): void => + fs.writeFileSync(path.join(TEMP_DB, name), content); +const read = (name: string): string => fs.readFileSync(path.join(TEMP_DB, name), 'utf-8'); + +/** + * Drive the agent side (issuer L1, user L2, merchant checkout, split L3) and + * persist every artifact under TEMP_DB exactly as the role servers expect. + */ +async function runAgentSide(amountMaxCents: number) { + const issuer = await makeKey('issuer'); + const user = await makeKey('user'); + const agent = await makeKey('agent'); + const merchant = await makeKey('merchant'); + + const l1 = await issueIssuerCredential({ userPublicJwk: user.publicKey, issuer, sub: 'u', iat: NOW }); + const l2 = await createUserMandateAutonomous({ + l1Serialized: l1, + user, + agentPublicJwk: agent.publicKey, + agentKid: agent.kid, + promptSummary: 'racket', + iat: NOW, + merchants: MERCHANTS, + acceptableItems: ACCEPTABLE_ITEMS, + paymentInstrument: PAYMENT_INSTRUMENT, + amountMin: 0, + amountMax: amountMaxCents, + }); + persist('l1.sdjwt', l1); + persist('l2.sdjwt', l2); + + const racket = findProduct('BAB86345')!; + const checkoutJwt = await createCheckoutJwt([{ sku: racket.sku }], merchant); + const checkoutHash = checkoutHashFromJwt(checkoutJwt); + const f = await createAgentFulfillment({ + l2Serialized: l2, + agent, + checkoutJwt, + checkoutHash, + payee: MERCHANTS[0], + itemId: racket.sku, + amount: racket.price, // 27999 + paymentInstrument: PAYMENT_INSTRUMENT, + iat: NOW, + }); + const chk = 'chk_test'; + const pay = 'pay_test'; + persist(`${chk}.sdjwt`, f.l3CheckoutSerialized); + persist(`${chk}.l2.sdjwt`, f.l2CheckoutSerialized); + persist(`${pay}.sdjwt`, f.l3PaymentSerialized); + persist(`${pay}.l2.sdjwt`, f.l2PaymentSerialized); + + return { issuer, chk, pay, price: racket.price }; +} + +describe('Verifiable Intent — cross-role file contract', () => { + it('completes a purchase: merchant + network verify from disk with aud pinning', async () => { + const { issuer, chk, pay } = await runAgentSide(40000); + + // Merchant role: read its files, verify the checkout chain (aud-pinned). + const merchantResult = await verifyCheckoutChain({ + l1Serialized: read('l1.sdjwt'), + l2CheckoutSerialized: read(`${chk}.l2.sdjwt`), + l3CheckoutSerialized: read(`${chk}.sdjwt`), + issuerPublicJwk: issuer.publicKey, + currentTime: NOW, + expectedL3CheckoutAud: MERCHANT_AUD, + }); + expect(merchantResult.valid).toBe(true); + + // Network/CP role: read its files, verify the payment chain + constraints. + const networkOutcome = await verifyPaymentChainAndConstraints({ + l1Serialized: read('l1.sdjwt'), + l2PaymentSerialized: read(`${pay}.l2.sdjwt`), + l3PaymentSerialized: read(`${pay}.sdjwt`), + issuerPublicJwk: issuer.publicKey, + currentTime: NOW, + expectedL3PaymentAud: NETWORK_AUD, + }); + expect(networkOutcome.valid).toBe(true); + expect(networkOutcome.constraints?.satisfied).toBe(true); + }); + + it('rejects settlement when the amount exceeds the mandate cap', async () => { + const { issuer, pay } = await runAgentSide(20000); // cap below the 27999 price + + const networkOutcome = await verifyPaymentChainAndConstraints({ + l1Serialized: read('l1.sdjwt'), + l2PaymentSerialized: read(`${pay}.l2.sdjwt`), + l3PaymentSerialized: read(`${pay}.sdjwt`), + issuerPublicJwk: issuer.publicKey, + currentTime: NOW, + expectedL3PaymentAud: NETWORK_AUD, + }); + expect(networkOutcome.result.valid).toBe(true); // chain is cryptographically valid + expect(networkOutcome.valid).toBe(false); // but the amount violates the mandate + expect(networkOutcome.constraints?.satisfied).toBe(false); + }); +}); From c0d52bfd4261a5807a43394bb758b8e768730716 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 6 Jun 2026 21:11:31 -0500 Subject: [PATCH 10/17] test(typescript): add immediate (2-layer) flow rejection tests The human-present / immediate flow (createUserMandateImmediate, the TS analog of immediate_flow.py) had a happy-path test but no rejection coverage. Add two immediate-specific cases: L2 verified against a different L1 fails the sd_hash L2->L1 binding, and verification against the wrong issuer key fails. Brings the immediate path to parity with the autonomous rejection tests. tsc + lint clean; unit suite 20/20. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 2 +- .../test/unit/vi-chain-negative.test.ts | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md index 835e469c..ee833df3 100644 --- a/code/samples/typescript/BACKLOG.md +++ b/code/samples/typescript/BACKLOG.md @@ -7,7 +7,7 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + ## Test coverage & hardening - [ ] Enable coverage: `npm i -D @vitest/coverage-v8` (not yet installed); add a unit-only `"test:coverage:unit": "vitest run test/unit --coverage"` script (the existing `test:coverage` also runs e2e) + a coverage block in `vitest.config.ts`; report coverage for `src/common/vi`. - [ ] Strengthen `src/common/vi` negative-path tests (see `test/unit/vi-chain-negative.test.ts`): add a tampered-L2 case (mutate a disclosed claim → chain invalid) and a kid-mismatch case (L3 signed by a key whose kid ≠ L2 `cnf.kid` → invalid). -- [ ] Add immediate-flow rejection tests: bad issuer signature, and an L2 whose `sd_hash` does not match L1 → `verifyChain` invalid. +- [x] Add immediate-flow rejection tests: wrong issuer key, and an L2 whose `sd_hash` does not match L1 (verified against a different L1) → `verifyChain` invalid. In `test/unit/vi-chain-negative.test.ts`. - [ ] Constraint-checker edge cases: amount exactly at the cap (allowed), one cent over (rejected), currency mismatch, empty `acceptable_items` (wildcard). ## Make the MCP role logic testable without servers diff --git a/code/samples/typescript/test/unit/vi-chain-negative.test.ts b/code/samples/typescript/test/unit/vi-chain-negative.test.ts index 1904c8e6..6d1830eb 100644 --- a/code/samples/typescript/test/unit/vi-chain-negative.test.ts +++ b/code/samples/typescript/test/unit/vi-chain-negative.test.ts @@ -25,6 +25,7 @@ import { createCheckoutJwt, checkoutHashFromJwt, createUserMandateAutonomous, + createUserMandateImmediate, decodeSdJwt, findProduct, generateEs256Key, @@ -163,3 +164,55 @@ describe('Verifiable Intent — rejection paths', () => { ).rejects.toThrow(); }); }); + +describe('Verifiable Intent — immediate (2-layer) rejection paths', () => { + it('rejects an immediate L2 verified against a different L1 (sd_hash binding)', async () => { + const issuer = await makeKey('issuer'); + const user = await makeKey('user'); + const merchant = await makeKey('merchant'); + // Two issuer credentials for the SAME user key but different content → different serialization. + const l1a = await issueIssuerCredential({ userPublicJwk: user.publicKey, issuer, sub: 'alice', iat: NOW }); + const l1b = await issueIssuerCredential({ userPublicJwk: user.publicKey, issuer, sub: 'bob', iat: NOW }); + const checkoutJwt = await createCheckoutJwt([{ sku: 'BAB86345' }], merchant); + const l2 = await createUserMandateImmediate({ + l1Serialized: l1a, + user, + checkoutJwt, + paymentInstrument: PAYMENT_INSTRUMENT, + payee: MERCHANTS[0], + amount: 27999, + iat: NOW, + }); + // L2's sd_hash binds it to l1a; verifying it against l1b must fail. + const result = await verifyChain(decodeSdJwt(l1b), decodeSdJwt(l2), { + issuerPublicJwk: issuer.publicKey, + l1Serialized: l1b, + currentTime: NOW, + }); + expect(result.valid).toBe(false); + }); + + it('rejects an immediate chain verified against the wrong issuer key', async () => { + const issuer = await makeKey('issuer'); + const user = await makeKey('user'); + const merchant = await makeKey('merchant'); + const wrongIssuer = await makeKey('issuer'); + const l1 = await issueIssuerCredential({ userPublicJwk: user.publicKey, issuer, sub: 'u', iat: NOW }); + const checkoutJwt = await createCheckoutJwt([{ sku: 'BAB86345' }], merchant); + const l2 = await createUserMandateImmediate({ + l1Serialized: l1, + user, + checkoutJwt, + paymentInstrument: PAYMENT_INSTRUMENT, + payee: MERCHANTS[0], + amount: 27999, + iat: NOW, + }); + const result = await verifyChain(decodeSdJwt(l1), decodeSdJwt(l2), { + issuerPublicJwk: wrongIssuer.publicKey, + l1Serialized: l1, + currentTime: NOW, + }); + expect(result.valid).toBe(false); + }); +}); From 0fd6c820d8bac6a6ab59f39fd46cf6abb29081bc Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 6 Jun 2026 21:14:43 -0500 Subject: [PATCH 11/17] test(typescript): add v8 coverage for the VI integration core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make gaps measurable: add @vitest/coverage-v8, a unit-only `test:coverage:unit` script, and a coverage block in vitest.config.ts scoped to src/common/vi (the role servers/mandate-tools need stdio servers, so they're e2e-covered). Baseline: 85.7% stmts / 82.5% branch / 73.9% funcs / 85.6% lines. Surfaced a real gap — keys.ts (the file-backed key store every role server uses) is 0% covered; logged a follow-up to test it. npm-link to @verifiable-intent/core verified intact after install. tsc + lint clean; unit suite 20/20. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 3 +- code/samples/typescript/package-lock.json | 550 +++++++++++++++------- code/samples/typescript/package.json | 4 +- code/samples/typescript/vitest.config.ts | 9 + 4 files changed, 404 insertions(+), 162 deletions(-) diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md index ee833df3..4983fd08 100644 --- a/code/samples/typescript/BACKLOG.md +++ b/code/samples/typescript/BACKLOG.md @@ -5,7 +5,8 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + `npx vitest run test/unit` all green). Check items off (`- [x]`) when done. ## Test coverage & hardening -- [ ] Enable coverage: `npm i -D @vitest/coverage-v8` (not yet installed); add a unit-only `"test:coverage:unit": "vitest run test/unit --coverage"` script (the existing `test:coverage` also runs e2e) + a coverage block in `vitest.config.ts`; report coverage for `src/common/vi`. +- [x] Enable coverage: `@vitest/coverage-v8` + `test:coverage:unit` script + a coverage block in `vitest.config.ts` (scoped to `src/common/vi`). Baseline: **85.7% stmts / 82.5% branch / 73.9% funcs / 85.6% lines**. +- [ ] **Coverage gap:** `keys.ts` is **0%** (`loadOrCreateViKey` / `loadViPublicJwk` — the file-backed key store used by every role server is untested); `fixtures.ts` 66% (`getCatalog`). Add a `keys.ts` round-trip test (generate → persist → reload returns same jwk + stable kid; `loadViPublicJwk` on a missing key → null) in a temp `TEMP_DB`. - [ ] Strengthen `src/common/vi` negative-path tests (see `test/unit/vi-chain-negative.test.ts`): add a tampered-L2 case (mutate a disclosed claim → chain invalid) and a kid-mismatch case (L3 signed by a key whose kid ≠ L2 `cnf.kid` → invalid). - [x] Add immediate-flow rejection tests: wrong issuer key, and an L2 whose `sd_hash` does not match L1 (verified against a different L1) → `verifyChain` invalid. In `test/unit/vi-chain-negative.test.ts`. - [ ] Constraint-checker edge cases: amount exactly at the cap (allowed), one cent over (rejected), currency mismatch, empty `acceptable_items` (wildcard). diff --git a/code/samples/typescript/package-lock.json b/code/samples/typescript/package-lock.json index d2ff5822..1495a0df 100644 --- a/code/samples/typescript/package-lock.json +++ b/code/samples/typescript/package-lock.json @@ -31,6 +31,7 @@ "@opentelemetry/sdk-trace-node": "^2.1.0", "@sd-jwt/core": "^0.19.0", "@sd-jwt/crypto-nodejs": "^0.19.0", + "@verifiable-intent/core": "file:../../../../verifiable-intent/typescript", "dotenv": "^17.4.2", "express": "^5.1.0", "uuid": "^13.0.0", @@ -44,6 +45,7 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.53.1", "@typescript-eslint/parser": "^8.53.1", + "@vitest/coverage-v8": "^4.1.8", "concurrently": "^9.1.2", "eslint": "^9.39.4", "eslint-import-resolver-typescript": "^4.4.4", @@ -58,6 +60,30 @@ "node": ">=20.0.0" } }, + "../../../../verifiable-intent/typescript": { + "name": "@verifiable-intent/core", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@sd-jwt/core": "^0.19.0", + "@sd-jwt/crypto-nodejs": "^0.19.0", + "@sd-jwt/types": "^0.19.0" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@types/node": "^22.7.0", + "eslint": "^9.13.0", + "publint": "^0.3.21", + "tsdown": "^0.22.1", + "typescript": "^5.6.0", + "typescript-eslint": "^8.10.0", + "unrun": "^0.2.39", + "vitest": "^4.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@a2a-js/sdk": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-0.3.13.tgz", @@ -368,6 +394,66 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@clack/core": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz", @@ -412,9 +498,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, @@ -424,9 +510,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -2231,6 +2317,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "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", @@ -2238,6 +2334,17 @@ "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/@js-joda/core": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.7.0.tgz", @@ -3787,9 +3894,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.124.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", - "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "dev": true, "license": "MIT", "funding": { @@ -3861,9 +3968,9 @@ "license": "BSD-3-Clause" }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "cpu": [ "arm64" ], @@ -3878,9 +3985,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" ], @@ -3895,9 +4002,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "cpu": [ "x64" ], @@ -3912,9 +4019,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "cpu": [ "x64" ], @@ -3929,9 +4036,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", - "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "cpu": [ "arm" ], @@ -3946,9 +4053,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "cpu": [ "arm64" ], @@ -3963,9 +4070,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "cpu": [ "arm64" ], @@ -3980,9 +4087,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "cpu": [ "ppc64" ], @@ -3997,9 +4104,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "cpu": [ "s390x" ], @@ -4014,9 +4121,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "cpu": [ "x64" ], @@ -4031,9 +4138,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "cpu": [ "x64" ], @@ -4048,9 +4155,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "cpu": [ "arm64" ], @@ -4065,9 +4172,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", - "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "cpu": [ "wasm32" ], @@ -4075,12 +4182,12 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.9.2", - "@emnapi/runtime": "1.9.2", - "@napi-rs/wasm-runtime": "^1.1.3" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { @@ -4103,9 +4210,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "cpu": [ "arm64" ], @@ -4120,9 +4227,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "cpu": [ "x64" ], @@ -4137,9 +4244,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", - "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "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" }, @@ -4995,17 +5102,52 @@ "win32" ] }, + "node_modules/@verifiable-intent/core": { + "resolved": "../../../../verifiable-intent/typescript", + "link": true + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", + "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.8", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.8", + "vitest": "4.1.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", - "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -5014,9 +5156,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", - "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "dev": true, "license": "MIT", "dependencies": { @@ -5027,13 +5169,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", - "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.4", + "@vitest/utils": "4.1.8", "pathe": "^2.0.3" }, "funding": { @@ -5041,14 +5183,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", - "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.4", - "@vitest/utils": "4.1.4", + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -5057,9 +5199,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", - "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", "dev": true, "license": "MIT", "funding": { @@ -5067,13 +5209,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", - "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.4", + "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -5421,6 +5563,18 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.3.tgz", + "integrity": "sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -8239,6 +8393,13 @@ ], "license": "MIT" }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -8984,6 +9145,58 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jose": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", @@ -9005,6 +9218,13 @@ "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", "license": "MIT" }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -9680,6 +9900,34 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-fetch-happen": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", @@ -10110,9 +10358,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -10861,9 +11109,9 @@ } }, "node_modules/postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -10881,7 +11129,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -11397,14 +11645,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", - "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.124.0", - "@rolldown/pluginutils": "1.0.0-rc.15" + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -11413,21 +11661,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-x64": "1.0.0-rc.15", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" } }, "node_modules/router": { @@ -12517,9 +12765,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -13493,19 +13741,19 @@ } }, "node_modules/vitest": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", - "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.4", - "@vitest/mocker": "4.1.4", - "@vitest/pretty-format": "4.1.4", - "@vitest/runner": "4.1.4", - "@vitest/snapshot": "4.1.4", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -13533,12 +13781,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.4", - "@vitest/browser-preview": "4.1.4", - "@vitest/browser-webdriverio": "4.1.4", - "@vitest/coverage-istanbul": "4.1.4", - "@vitest/coverage-v8": "4.1.4", - "@vitest/ui": "4.1.4", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -14051,13 +14299,13 @@ } }, "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", - "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.4", + "@vitest/spy": "4.1.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -14135,17 +14383,17 @@ } }, "node_modules/vitest/node_modules/vite": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", - "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.15", - "tinyglobby": "^0.2.15" + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -14161,7 +14409,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -14497,24 +14745,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, - "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/code/samples/typescript/package.json b/code/samples/typescript/package.json index 56536ab0..c6555088 100644 --- a/code/samples/typescript/package.json +++ b/code/samples/typescript/package.json @@ -9,6 +9,7 @@ "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", + "test:coverage:unit": "vitest run test/unit --coverage", "cli": "npx adk run src/roles/shopping-agent/agent.ts", "dev": "concurrently \"npx tsx src/roles/shopping-agent/server.ts\" \"npx tsx src/roles/credentials-provider-agent/server.ts\" \"npx tsx src/roles/merchant-payment-processor-agent/server.ts\" \"npx tsx src/roles/merchant-agent/server.ts\" \"npx adk web src/web-agents --host localhost -p 3001\"" }, @@ -26,7 +27,6 @@ }, "dependencies": { "@a2a-js/sdk": "^0.3.13", - "@verifiable-intent/core": "file:../../../../verifiable-intent/typescript", "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", "@google-cloud/storage": "^7.19.0", @@ -48,6 +48,7 @@ "@opentelemetry/sdk-trace-node": "^2.1.0", "@sd-jwt/core": "^0.19.0", "@sd-jwt/crypto-nodejs": "^0.19.0", + "@verifiable-intent/core": "file:../../../../verifiable-intent/typescript", "dotenv": "^17.4.2", "express": "^5.1.0", "uuid": "^13.0.0", @@ -61,6 +62,7 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.53.1", "@typescript-eslint/parser": "^8.53.1", + "@vitest/coverage-v8": "^4.1.8", "concurrently": "^9.1.2", "eslint": "^9.39.4", "eslint-import-resolver-typescript": "^4.4.4", diff --git a/code/samples/typescript/vitest.config.ts b/code/samples/typescript/vitest.config.ts index 6568db59..eb4e7f7a 100644 --- a/code/samples/typescript/vitest.config.ts +++ b/code/samples/typescript/vitest.config.ts @@ -7,6 +7,15 @@ export default defineConfig({ include: ['test/**/*.test.ts'], testTimeout: 30000, hookTimeout: 15000, + coverage: { + provider: 'v8', + reporter: ['text', 'json-summary'], + // Scope to the Verifiable Intent integration core (the unit-tested glue). + // The role servers / mandate-tools need stdio servers to exercise, so they + // are covered by e2e, not the unit suite — see BACKLOG.md. + include: ['src/common/vi/**/*.ts'], + exclude: ['src/common/vi/index.ts'], + }, }, resolve: { extensions: ['.ts', '.js'], From 184fd5f2f36017882579e00e6dd52515fd1ec2dc Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 6 Jun 2026 21:16:17 -0500 Subject: [PATCH 12/17] test(typescript): cover the file-backed key store (keys.ts 0% -> 100%) keys.ts (loadOrCreateViKey / loadViPublicJwk) is the shared key store every role server uses to persist + resolve ES256 keys across processes, and it had no coverage. Add round-trip persist/reload, per-role kid assignment + fallback, legacy (no-kid) file format, loadViPublicJwk missing->null with no private-scalar leak, and a persisted issuer key signing a verifiable credential. src/common/vi coverage 85.7% -> 96.2% stmts, 73.9% -> 91.3% funcs. tsc + lint clean; unit suite 25/25. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 2 +- .../typescript/test/unit/vi-keys.test.ts | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 code/samples/typescript/test/unit/vi-keys.test.ts diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md index 4983fd08..0d54cb59 100644 --- a/code/samples/typescript/BACKLOG.md +++ b/code/samples/typescript/BACKLOG.md @@ -6,7 +6,7 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + ## Test coverage & hardening - [x] Enable coverage: `@vitest/coverage-v8` + `test:coverage:unit` script + a coverage block in `vitest.config.ts` (scoped to `src/common/vi`). Baseline: **85.7% stmts / 82.5% branch / 73.9% funcs / 85.6% lines**. -- [ ] **Coverage gap:** `keys.ts` is **0%** (`loadOrCreateViKey` / `loadViPublicJwk` — the file-backed key store used by every role server is untested); `fixtures.ts` 66% (`getCatalog`). Add a `keys.ts` round-trip test (generate → persist → reload returns same jwk + stable kid; `loadViPublicJwk` on a missing key → null) in a temp `TEMP_DB`. +- [x] **Coverage gap closed:** `keys.ts` now **100%** — round-trip persist/reload, per-role kids + fallback, legacy (no-kid) format, `loadViPublicJwk` missing→null + no private-scalar leak, and a persisted key signing a verifiable credential (`test/unit/vi-keys.test.ts`). Overall `src/common/vi` now **96.2% stmts / 91.3% funcs**. (`fixtures.ts` `getCatalog` still uncovered — minor.) - [ ] Strengthen `src/common/vi` negative-path tests (see `test/unit/vi-chain-negative.test.ts`): add a tampered-L2 case (mutate a disclosed claim → chain invalid) and a kid-mismatch case (L3 signed by a key whose kid ≠ L2 `cnf.kid` → invalid). - [x] Add immediate-flow rejection tests: wrong issuer key, and an L2 whose `sd_hash` does not match L1 (verified against a different L1) → `verifyChain` invalid. In `test/unit/vi-chain-negative.test.ts`. - [ ] Constraint-checker edge cases: amount exactly at the cap (allowed), one cent over (rejected), currency mismatch, empty `acceptable_items` (wildcard). diff --git a/code/samples/typescript/test/unit/vi-keys.test.ts b/code/samples/typescript/test/unit/vi-keys.test.ts new file mode 100644 index 00000000..7c9fe4aa --- /dev/null +++ b/code/samples/typescript/test/unit/vi-keys.test.ts @@ -0,0 +1,84 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Tests for the file-backed ES256 key store (src/common/vi/keys.ts) — the + * shared infrastructure every role server uses to persist + resolve keys across + * processes. Previously 0% covered. Runs against a throwaway temp TEMP_DB. + */ + +import { afterEach, beforeEach, describe, it, expect } from 'vitest'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { + ROLE_KIDS, + decodeSdJwt, + generateEs256Key, + issueIssuerCredential, + loadOrCreateViKey, + loadViPublicJwk, + verifySdJwtSignature, +} from '../../src/common/vi/index.js'; + +let TEMP_DB: string; +beforeEach(() => { + TEMP_DB = fs.mkdtempSync(path.join(os.tmpdir(), 'vi-keys-')); +}); +afterEach(() => { + fs.rmSync(TEMP_DB, { recursive: true, force: true }); +}); + +describe('VI key store', () => { + it('generates + persists a keypair on first use and reloads it unchanged', async () => { + const first = await loadOrCreateViKey(TEMP_DB, 'issuer'); + expect(first.publicKey.kty).toBe('EC'); + expect(first.privateKey.d).toBeTruthy(); // private scalar present + expect(fs.existsSync(path.join(TEMP_DB, 'issuer_key.jwk.json'))).toBe(true); + + const second = await loadOrCreateViKey(TEMP_DB, 'issuer'); + expect(second).toEqual(first); // same jwk + kid, loaded from disk + }); + + it('assigns the documented per-role kids and falls back to -key-1', async () => { + for (const role of ['issuer', 'user', 'agent', 'merchant', 'psp']) { + const pair = await loadOrCreateViKey(TEMP_DB, role); + expect(pair.kid).toBe(ROLE_KIDS[role]); + } + const unknown = await loadOrCreateViKey(TEMP_DB, 'auditor'); + expect(unknown.kid).toBe('auditor-key-1'); + }); + + it('loadViPublicJwk returns the public jwk after creation and null when missing', async () => { + expect(loadViPublicJwk(TEMP_DB, 'merchant')).toBeNull(); + const pair = await loadOrCreateViKey(TEMP_DB, 'merchant'); + const pub = loadViPublicJwk(TEMP_DB, 'merchant'); + expect(pub).toEqual(pair.publicKey); + expect((pub as { d?: string }).d).toBeUndefined(); // no private scalar leaked + }); + + it('loads a legacy keypair (no kid) with the default kid', async () => { + const { publicKey, privateKey } = await generateEs256Key(); + fs.writeFileSync(path.join(TEMP_DB, 'merchant_key.jwk.json'), JSON.stringify({ publicKey, privateKey })); + const pair = await loadOrCreateViKey(TEMP_DB, 'merchant'); + expect(pair.kid).toBe('merchant-key-1'); + expect(pair.publicKey).toEqual(publicKey); + }); + + it('a persisted issuer key actually signs a verifiable credential', async () => { + const issuer = await loadOrCreateViKey(TEMP_DB, 'issuer'); + const user = await loadOrCreateViKey(TEMP_DB, 'user'); + const l1 = await issueIssuerCredential({ + userPublicJwk: user.publicKey, + issuer, + sub: 'u', + iat: 1_900_000_000, + }); + expect(await verifySdJwtSignature(decodeSdJwt(l1), issuer.publicKey)).toBe(true); + }); +}); From 4570c58b24e2a733e98027ebb22faf5ae82fc3b5 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 6 Jun 2026 21:18:16 -0500 Subject: [PATCH 13/17] test(typescript): cover payment amount/currency constraint boundaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The over-budget case was tested but not the exact cap boundary. Add: amount exactly at the cap is allowed, one minor unit over is rejected, and a currency differing from the mandate is rejected — through the network verification path (verifyPaymentChainAndConstraints). Confirms the spend cap is enforced exactly. tsc + lint clean; unit suite 28/28. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 2 +- .../test/unit/vi-constraints.test.ts | 108 ++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 code/samples/typescript/test/unit/vi-constraints.test.ts diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md index 0d54cb59..f426e16f 100644 --- a/code/samples/typescript/BACKLOG.md +++ b/code/samples/typescript/BACKLOG.md @@ -9,7 +9,7 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + - [x] **Coverage gap closed:** `keys.ts` now **100%** — round-trip persist/reload, per-role kids + fallback, legacy (no-kid) format, `loadViPublicJwk` missing→null + no private-scalar leak, and a persisted key signing a verifiable credential (`test/unit/vi-keys.test.ts`). Overall `src/common/vi` now **96.2% stmts / 91.3% funcs**. (`fixtures.ts` `getCatalog` still uncovered — minor.) - [ ] Strengthen `src/common/vi` negative-path tests (see `test/unit/vi-chain-negative.test.ts`): add a tampered-L2 case (mutate a disclosed claim → chain invalid) and a kid-mismatch case (L3 signed by a key whose kid ≠ L2 `cnf.kid` → invalid). - [x] Add immediate-flow rejection tests: wrong issuer key, and an L2 whose `sd_hash` does not match L1 (verified against a different L1) → `verifyChain` invalid. In `test/unit/vi-chain-negative.test.ts`. -- [ ] Constraint-checker edge cases: amount exactly at the cap (allowed), one cent over (rejected), currency mismatch, empty `acceptable_items` (wildcard). +- [x] Constraint amount/currency boundaries: at-cap allowed, +1 minor unit rejected, currency mismatch rejected (`test/unit/vi-constraints.test.ts`). (Empty `acceptable_items` line-items wildcard still untested — minor.) ## Make the MCP role logic testable without servers - [ ] Extract the pure logic of merchant-agent-mcp `complete_checkout`, credentials-provider-mcp `issue_payment_credential`, and merchant `create_checkout` into small exported functions (no `McpServer`/stdio), and unit-test their happy + error paths against the persisted-file contract in a temp `TEMP_DB`. diff --git a/code/samples/typescript/test/unit/vi-constraints.test.ts b/code/samples/typescript/test/unit/vi-constraints.test.ts new file mode 100644 index 00000000..1dffb382 --- /dev/null +++ b/code/samples/typescript/test/unit/vi-constraints.test.ts @@ -0,0 +1,108 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Boundary tests for the payment amount/currency constraint — the core VI + * guarantee that an autonomous agent cannot spend beyond the user's mandate. + * Exercises the exact cap boundary and a currency mismatch through the network + * verification path. Runs in-process (no servers / no Gemini). + */ + +import { describe, it, expect } from 'vitest'; +import { + ACCEPTABLE_ITEMS, + MERCHANTS, + NETWORK_AUD, + PAYMENT_INSTRUMENT, + ROLE_KIDS, + type ViKeyPair, + createAgentFulfillment, + createCheckoutJwt, + checkoutHashFromJwt, + createUserMandateAutonomous, + findProduct, + generateEs256Key, + issueIssuerCredential, + verifyPaymentChainAndConstraints, +} from '../../src/common/vi/index.js'; + +const NOW = 1_900_000_000; + +async function makeKey(name: string): Promise { + const { publicKey, privateKey } = await generateEs256Key(); + return { publicKey, privateKey, kid: ROLE_KIDS[name] ?? `${name}-key-1` }; +} + +/** Build + verify a payment chain with a given mandate cap and fulfilled amount/currency. */ +async function settle(opts: { amountMax: number; amount: number; currency?: string }) { + const issuer = await makeKey('issuer'); + const user = await makeKey('user'); + const agent = await makeKey('agent'); + const merchant = await makeKey('merchant'); + + const l1 = await issueIssuerCredential({ userPublicJwk: user.publicKey, issuer, sub: 'u', iat: NOW }); + const l2 = await createUserMandateAutonomous({ + l1Serialized: l1, + user, + agentPublicJwk: agent.publicKey, + agentKid: agent.kid, + promptSummary: 'racket', + iat: NOW, + merchants: MERCHANTS, + acceptableItems: ACCEPTABLE_ITEMS, + paymentInstrument: PAYMENT_INSTRUMENT, + amountMin: 0, + amountMax: opts.amountMax, + currency: 'USD', + }); + const racket = findProduct('BAB86345')!; + const checkoutJwt = await createCheckoutJwt([{ sku: racket.sku }], merchant); + const checkoutHash = checkoutHashFromJwt(checkoutJwt); + const f = await createAgentFulfillment({ + l2Serialized: l2, + agent, + checkoutJwt, + checkoutHash, + payee: MERCHANTS[0], + itemId: racket.sku, + amount: opts.amount, + currency: opts.currency ?? 'USD', + paymentInstrument: PAYMENT_INSTRUMENT, + iat: NOW, + }); + return verifyPaymentChainAndConstraints({ + l1Serialized: l1, + l2PaymentSerialized: f.l2PaymentSerialized, + l3PaymentSerialized: f.l3PaymentSerialized, + issuerPublicJwk: issuer.publicKey, + currentTime: NOW, + expectedL3PaymentAud: NETWORK_AUD, + }); +} + +describe('VI payment amount/currency constraint boundaries', () => { + it('allows an amount exactly at the cap', async () => { + const outcome = await settle({ amountMax: 27999, amount: 27999 }); + expect(outcome.constraints?.satisfied).toBe(true); + expect(outcome.valid).toBe(true); + }); + + it('rejects an amount one minor unit over the cap', async () => { + const outcome = await settle({ amountMax: 27998, amount: 27999 }); + expect(outcome.result.valid).toBe(true); // chain is cryptographically valid + expect(outcome.constraints?.satisfied).toBe(false); // amount violates the cap + expect(outcome.valid).toBe(false); + }); + + it('rejects a currency that differs from the mandate', async () => { + const outcome = await settle({ amountMax: 40000, amount: 27999, currency: 'EUR' }); + expect(outcome.result.valid).toBe(true); + expect(outcome.constraints?.satisfied).toBe(false); + expect(outcome.valid).toBe(false); + }); +}); From 3c9cd3b4d47a1dcbb9a1ba9be054cafd5cb6f891 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 6 Jun 2026 21:21:21 -0500 Subject: [PATCH 14/17] fix(typescript): consent resolves item_id before signing L2 (prompt audit) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Iteration 7 audited every v2 prompt against the current tool signatures: all consistent after the migration. It also confirmed the item-id binding gap is a prompt-ordering bug — consent signed L2 before search_inventory ran, so L2's acceptable_items got default skus while the merchant serves generated slug ids, breaking createAgentFulfillment's item match. Fix: consent now calls search_inventory FIRST and threads the resolved item_id into assembleAndSignMandates (required), so the signed mandate binds the exact item the monitoring/purchase agents will buy. (Prompt-behavior change — needs e2e validation; gates unaffected.) tsc + lint clean; unit suite 28/28. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 2 +- .../roles/shopping-agent-v2/prompts/consent.ts | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md index f426e16f..73703176 100644 --- a/code/samples/typescript/BACKLOG.md +++ b/code/samples/typescript/BACKLOG.md @@ -14,7 +14,7 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + ## Make the MCP role logic testable without servers - [ ] Extract the pure logic of merchant-agent-mcp `complete_checkout`, credentials-provider-mcp `issue_payment_credential`, and merchant `create_checkout` into small exported functions (no `McpServer`/stdio), and unit-test their happy + error paths against the persisted-file contract in a temp `TEMP_DB`. - [x] Add a deterministic cross-role integration test via the persisted-file contract (`test/unit/vi-integration.test.ts`): agent writes `l1/l2/chk_*/pay_*` to a temp dir, merchant + network read them back and verify with aud pinning; asserts a valid purchase and an over-budget rejection. No Gemini/servers. (Tool-level tests of the actual `*.execute` handlers are still worth adding — see item above.) -- [ ] **Gap (flow):** the merchant MCP serves generated slug item ids (`_0` from `generateInventoryEntry`), but the L2 `acceptable_items` default to the VI catalog skus (BAB86345). If consent signs L2 before `search_inventory` supplies the merchant item id, `createAgentFulfillment` can't match the item disclosure and throws. Fix: have the merchant serve the VI catalog (sku ids), or require consent to thread the merchant `item_id` into the mandate. +- [~] **Gap (flow):** merchant MCP serves generated slug item ids (`_0`) but L2 `acceptable_items` defaulted to VI skus, so `createAgentFulfillment` couldn't match the item. **Mitigated (iter 7):** consent prompt now calls `search_inventory` FIRST and threads the resolved `item_id` into `assembleAndSignMandates` (REQUIRED), so L2 binds the exact merchant item. Prompt audit also confirmed all v2 prompts match the current tool signatures. **Still needs e2e validation** (prompt behavior isn't unit-verifiable); a `merchant-serves-VI-catalog` alternative remains an option. ## Robustness - [x] Pin `expectedL3*Aud` in the role `verifyChain` calls so a presentation addressed to a different party is rejected — merchant pins `MERCHANT_AUD`, CP pins `NETWORK_AUD`, agent stamps both via shared fixtures constants. Test in `test/unit/vi-chain-negative.test.ts`. diff --git a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts index f56b594c..0ce1a106 100644 --- a/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts +++ b/code/samples/typescript/src/roles/shopping-agent-v2/prompts/consent.ts @@ -25,12 +25,17 @@ Conversation memory: scan all prior messages and build active_product (full natu Workflow: A) First contact: when the user shows purchase intent for a limited/timed item, write short prose (offer to buy for them, a plausible drop time, typical price, ask their budget / permission). Do NOT call any tool yet. If the user asks to start over / reset, call resetTempDb first. -B) After the user agrees on a budget (or says "yes" to your price): call assembleAndSignMandates with: - - natural_language_description = active_product - - constraint_price_cap = active_budget - - expires_at_iso = an ISO 8601 timestamp ~1 hour from now - - allowed_merchants = optional list if the user named specific merchants - - item_id = the merchant catalog item id, if you already have one from search_inventory / check_product +B) After the user agrees on a budget (or says "yes" to your price): + 1. FIRST call search_inventory(product_description = active_product, constraint_price_cap = active_budget) + to resolve the merchant catalog item_id for this product. Keep matches[0].item_id. + 2. THEN call assembleAndSignMandates with: + - natural_language_description = active_product + - constraint_price_cap = active_budget + - expires_at_iso = an ISO 8601 timestamp ~1 hour from now + - allowed_merchants = optional list if the user named specific merchants + - item_id = the item_id from step 1 (REQUIRED: the signed mandate's acceptable_items must bind + the exact item the monitoring/purchase agents will later check_product / assemble_cart, or the + Layer 3 fulfillment cannot match it) This issues the Layer 1 issuer credential and signs the Layer 2 autonomous user mandate (a real SD-JWT delegation chain) with the amount-range, merchant and acceptable-item constraints, and returns the credential_id / mandate_id and hashes, which persist for the downstream agents. From bf35c982426e5fafd32bacda4975a922d3071ffb Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 6 Jun 2026 21:23:24 -0500 Subject: [PATCH 15/17] test(typescript): reject impostor-agent L3 and tampered L2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lock two core VI guarantees: only the cnf-bound agent can fulfill (an L3 signed by a non-delegated agent key — even with a matching kid — fails verification against L2's cnf.jwk), and a mutated L2 signature is rejected. Both via the network verification path. tsc + lint clean; unit suite 30/30. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 2 +- .../test/unit/vi-chain-negative.test.ts | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md index 73703176..ee73eb49 100644 --- a/code/samples/typescript/BACKLOG.md +++ b/code/samples/typescript/BACKLOG.md @@ -7,7 +7,7 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + ## Test coverage & hardening - [x] Enable coverage: `@vitest/coverage-v8` + `test:coverage:unit` script + a coverage block in `vitest.config.ts` (scoped to `src/common/vi`). Baseline: **85.7% stmts / 82.5% branch / 73.9% funcs / 85.6% lines**. - [x] **Coverage gap closed:** `keys.ts` now **100%** — round-trip persist/reload, per-role kids + fallback, legacy (no-kid) format, `loadViPublicJwk` missing→null + no private-scalar leak, and a persisted key signing a verifiable credential (`test/unit/vi-keys.test.ts`). Overall `src/common/vi` now **96.2% stmts / 91.3% funcs**. (`fixtures.ts` `getCatalog` still uncovered — minor.) -- [ ] Strengthen `src/common/vi` negative-path tests (see `test/unit/vi-chain-negative.test.ts`): add a tampered-L2 case (mutate a disclosed claim → chain invalid) and a kid-mismatch case (L3 signed by a key whose kid ≠ L2 `cnf.kid` → invalid). +- [x] Strengthen `src/common/vi` negative-path tests: impostor-agent case (L3 signed by a non-delegated key whose kid matches but whose signature can't verify against L2 `cnf.jwk` → invalid) and tampered-L2 case (mutated L2 signature → invalid). In `test/unit/vi-chain-negative.test.ts`. - [x] Add immediate-flow rejection tests: wrong issuer key, and an L2 whose `sd_hash` does not match L1 (verified against a different L1) → `verifyChain` invalid. In `test/unit/vi-chain-negative.test.ts`. - [x] Constraint amount/currency boundaries: at-cap allowed, +1 minor unit rejected, currency mismatch rejected (`test/unit/vi-constraints.test.ts`). (Empty `acceptable_items` line-items wildcard still untested — minor.) diff --git a/code/samples/typescript/test/unit/vi-chain-negative.test.ts b/code/samples/typescript/test/unit/vi-chain-negative.test.ts index 6d1830eb..cbf285a9 100644 --- a/code/samples/typescript/test/unit/vi-chain-negative.test.ts +++ b/code/samples/typescript/test/unit/vi-chain-negative.test.ts @@ -18,6 +18,7 @@ import { ACCEPTABLE_ITEMS, MERCHANT_AUD, MERCHANTS, + NETWORK_AUD, PAYMENT_INSTRUMENT, ROLE_KIDS, type ViKeyPair, @@ -163,6 +164,71 @@ describe('Verifiable Intent — rejection paths', () => { }), ).rejects.toThrow(); }); + + it('rejects an L3 signed by a non-delegated agent key', async () => { + const issuer = await makeKey('issuer'); + const user = await makeKey('user'); + const delegated = await makeKey('agent'); // bound into L2 cnf.jwk + const impostor = await makeKey('agent'); // same kid, different key + const merchant = await makeKey('merchant'); + + const l1 = await issueIssuerCredential({ userPublicJwk: user.publicKey, issuer, sub: 'u', iat: NOW }); + const l2 = await createUserMandateAutonomous({ + l1Serialized: l1, + user, + agentPublicJwk: delegated.publicKey, + agentKid: delegated.kid, + promptSummary: 'racket', + iat: NOW, + merchants: MERCHANTS, + acceptableItems: ACCEPTABLE_ITEMS, + paymentInstrument: PAYMENT_INSTRUMENT, + amountMin: 0, + amountMax: 40000, + }); + const racket = findProduct('BAB86345')!; + const checkoutJwt = await createCheckoutJwt([{ sku: racket.sku }], merchant); + const checkoutHash = checkoutHashFromJwt(checkoutJwt); + // The impostor (not the cnf-bound agent) builds the fulfillment. + const f = await createAgentFulfillment({ + l2Serialized: l2, + agent: impostor, + checkoutJwt, + checkoutHash, + payee: MERCHANTS[0], + itemId: racket.sku, + amount: racket.price, + paymentInstrument: PAYMENT_INSTRUMENT, + iat: NOW, + }); + const outcome = await verifyPaymentChainAndConstraints({ + l1Serialized: l1, + l2PaymentSerialized: f.l2PaymentSerialized, + l3PaymentSerialized: f.l3PaymentSerialized, + issuerPublicJwk: issuer.publicKey, + currentTime: NOW, + expectedL3PaymentAud: NETWORK_AUD, + }); + expect(outcome.result.valid).toBe(false); // L3 not signed by the delegated agent key + }); + + it('rejects a tampered L2 presentation (mutated signature)', async () => { + const { issuer, l1, fulfillment } = await buildChain(); + // Flip the last char of the L2 base-JWT signature segment of the network view. + const parts = fulfillment.l2PaymentSerialized.split('~'); + const [h, p, sig] = parts[0].split('.'); + parts[0] = [h, p, sig.slice(0, -1) + (sig.endsWith('A') ? 'B' : 'A')].join('.'); + const tampered = parts.join('~'); + const outcome = await verifyPaymentChainAndConstraints({ + l1Serialized: l1, + l2PaymentSerialized: tampered, + l3PaymentSerialized: fulfillment.l3PaymentSerialized, + issuerPublicJwk: issuer.publicKey, + currentTime: NOW, + expectedL3PaymentAud: NETWORK_AUD, + }); + expect(outcome.result.valid).toBe(false); + }); }); describe('Verifiable Intent — immediate (2-layer) rejection paths', () => { From 8f95acb22a2c1d7ade1e64e94b5947371ac54f37 Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 6 Jun 2026 21:24:42 -0500 Subject: [PATCH 16/17] docs(typescript): add common/vi README (flow, contract, security properties) Capstone documentation for the Verifiable Intent integration: the L1->L2->L3 layer model, AP2 role->layer mapping diagram, the TEMP_DB cross-role file contract, the public flow API, and the security properties now enforced + tested (issuer trust, delegation binding, layer/audience binding, spend cap, freshness). Also records the remaining e2e-only gaps. tsc + lint clean; unit suite 30/30. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 2 +- .../typescript/src/common/vi/README.md | 85 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 code/samples/typescript/src/common/vi/README.md diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md index ee73eb49..6717385f 100644 --- a/code/samples/typescript/BACKLOG.md +++ b/code/samples/typescript/BACKLOG.md @@ -23,4 +23,4 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + - [ ] `verifyPaymentChainAndConstraints`: surface a clear error when the issuer key is missing, and test it. ## Docs -- [ ] Add `src/common/vi/README.md` documenting the L1→L2→L3 flow (diagram, field reference, who-stores-what), matching the integration. +- [x] Add `src/common/vi/README.md` documenting the L1→L2→L3 flow (layer model, role→layer mapping diagram, file contract, public API, and the security properties the tests enforce). diff --git a/code/samples/typescript/src/common/vi/README.md b/code/samples/typescript/src/common/vi/README.md new file mode 100644 index 00000000..929d5152 --- /dev/null +++ b/code/samples/typescript/src/common/vi/README.md @@ -0,0 +1,85 @@ + + +# `common/vi` — Verifiable Intent integration + +TypeScript replication of the official Python reference flow +(`verifiable_intent/python/examples`), wired into the AP2 sample. It is a thin, +per-role facade over the [`@verifiable-intent/core`](https://www.npmjs.com/package/@verifiable-intent/core) +library that produces a cryptographic, layered SD-JWT delegation chain so a +merchant and a payment network can independently prove an autonomous agent +purchased **exactly what the human authorized**. + +## The layer model + +A chain of signed credentials, each one binding the **next** signer's key +(`cnf.jwk`, RFC 7800) and the **previous** document's hash (`sd_hash`): + +| Layer | Signed by | Says | VI call | +|---|---|---|---| +| **L1** issuer credential | Credentials Provider | "this card belongs to the user" + binds the **user** key | `issueIssuerCredential` → `createLayer1` | +| **L2** user mandate | User | "I authorize **this agent** within these limits" + constraints | `createUserMandate{Autonomous,Immediate}` | +| **L3a** payment (→ network) | Agent | "pay $X to merchant M for txn T" | `createLayer3Payment` | +| **L3b** checkout (→ merchant) | Agent | "here is the final checkout JWT" | `createLayer3Checkout` | + +Two modes: **autonomous** (Human-Not-Present — L1→L2→split-L3, the agent acts +later within the mandate) and **immediate** (Human-Present — L1→L2 with final +values, no L3). Amounts are always **minor units (cents)**. + +## AP2 role → layer mapping + +``` + Credentials Provider User / shopping-agent-v2 Shopping Agent + = Issuer (L1) = User mandate (L2) = Agent (L3 split) + │ │ │ + │ L1 (binds user key) │ L2 (binds agent key, │ L3a → network + └────────────────────────► │ constraints) ────────► │ L3b → merchant + │ + merchant-agent-mcp ── verifyCheckoutChain (L1→L2→L3b, aud-pinned) ◄── L3b + credentials-provider-mcp ── verifyPaymentChainAndConstraints (L1→L2→L3a) ◄── L3a +``` + +## Cross-role file contract (TEMP_DB) + +The agent persists serialized SD-JWTs; the MCP servers read them back by id: + +| File | Written by | Read by | +|---|---|---| +| `l1.sdjwt`, `l2.sdjwt` | `assembleAndSignMandates` | merchant, CP | +| `.sdjwt`, `.l2.sdjwt` | `createMandateFulfillment` (merchant view) | merchant `complete_checkout` | +| `.sdjwt`, `.l2.sdjwt` | `createMandateFulfillment` (network view) | CP `issue_payment_credential` | +| `_key.jwk.json` | `loadOrCreateViKey` | every role | + +## Public API (`flow.ts`) + +- `issueIssuerCredential(params) → l1Serialized` +- `createUserMandateAutonomous(params) → l2Serialized` +- `createUserMandateImmediate(params) → l2Serialized` +- `createAgentFulfillment(params) → { l3PaymentSerialized, l3CheckoutSerialized, l2PaymentSerialized, l2CheckoutSerialized }` +- `verifyCheckoutChain(params) → ChainVerificationResult` *(merchant; pin `expectedL3CheckoutAud`)* +- `verifyPaymentChainAndConstraints(params) → { valid, errors, result, constraints }` *(network; pin `expectedL3PaymentAud`)* + +Plus `fixtures.ts` (scenario data + `MERCHANT_AUD`/`NETWORK_AUD`), `keys.ts` +(file-backed JWK key store), `checkout-jwt.ts` (merchant checkout JWS). + +## Security properties (enforced + tested) + +Verified in `test/unit/vi-*.test.ts` (28 of 30 unit tests cover this module; +`src/common/vi` ≈96% stmts / 91% funcs): + +- **Issuer trust** — chain fails closed without the issuer key; wrong key rejected. +- **Delegation binding** — only the `cnf`-bound agent key can sign a valid L3 + (an impostor key, even with a matching `kid`, is rejected). +- **Layer binding** — `sd_hash` ties L2→L1 and L3→L2; a tampered mandate fails. +- **Audience binding** — verifiers pin `aud`, rejecting a presentation addressed + to a different party. +- **Spend cap** — amount at-cap allowed, one minor unit over / wrong currency rejected. +- **Freshness** — expired L3 (exp ≤ iat+3600) rejected. + +## Known gaps (need a live e2e session to fix + verify) + +See `../../../BACKLOG.md`: payment-token↔checkout binding, replay/nonce dedup, +and full e2e validation of the item-id consent fix. These involve the running +MCP/A2A servers and a Gemini key, so they can't be exercised by the unit suite. From ce33fd1bcc5bb7beab540f5b3c565a071f4d22ba Mon Sep 17 00:00:00 2001 From: Miguel Velasquez Date: Sat, 6 Jun 2026 21:26:45 -0500 Subject: [PATCH 17/17] docs(typescript): log checkout-side constraint enforcement gap + loop status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Iteration 10 audit: verifyCheckoutChain (merchant) verifies chain structure + aud but never runs checkConstraints — only the network path enforces constraints. So the merchant doesn't independently verify the checked-out item is acceptable (it trusts the agent's self-check). Logged as an e2e-design follow-up. Marks the unit-testable security/coverage/docs backlog as essentially complete (src/common/vi ~96% stmts / 91% funcs, 30 tests); remaining items are e2e-only. Co-Authored-By: Claude Opus 4.8 (1M context) --- code/samples/typescript/BACKLOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/code/samples/typescript/BACKLOG.md b/code/samples/typescript/BACKLOG.md index 6717385f..1eaf2fa1 100644 --- a/code/samples/typescript/BACKLOG.md +++ b/code/samples/typescript/BACKLOG.md @@ -4,6 +4,15 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + `npx vitest run test/unit` all green). Check items off (`- [x]`) when done. +> **Status (after 10 improve-loop iterations):** the unit-testable security + +> coverage + docs backlog is essentially complete — `src/common/vi` ≈96% stmts / +> 91% funcs, 30 unit tests, all gates green. The remaining open items below are +> **e2e-only** (need the running MCP/A2A servers + a Gemini key to fix *and* +> verify): payment-token↔checkout binding, replay/nonce dedup, checkout-side +> constraint enforcement, the MCP-tool-extraction refactor, and live validation +> of the item-id consent fix. These should be done in a fresh, bounded session +> with the servers running — not this unbounded unit-only loop. + ## Test coverage & hardening - [x] Enable coverage: `@vitest/coverage-v8` + `test:coverage:unit` script + a coverage block in `vitest.config.ts` (scoped to `src/common/vi`). Baseline: **85.7% stmts / 82.5% branch / 73.9% funcs / 85.6% lines**. - [x] **Coverage gap closed:** `keys.ts` now **100%** — round-trip persist/reload, per-role kids + fallback, legacy (no-kid) format, `loadViPublicJwk` missing→null + no private-scalar leak, and a persisted key signing a verifiable credential (`test/unit/vi-keys.test.ts`). Overall `src/common/vi` now **96.2% stmts / 91.3% funcs**. (`fixtures.ts` `getCatalog` still uncovered — minor.) @@ -20,6 +29,7 @@ independently verifiable (`npx tsc --noEmit` + `npm run lint` + - [x] Pin `expectedL3*Aud` in the role `verifyChain` calls so a presentation addressed to a different party is rejected — merchant pins `MERCHANT_AUD`, CP pins `NETWORK_AUD`, agent stamps both via shared fixtures constants. Test in `test/unit/vi-chain-negative.test.ts`. - [ ] Replay protection: verifiers don't pin nonce/`transaction_id`, and used payment tokens / mandates aren't deduped — the same authorization could be replayed. Track spent nonces/tokens across the role servers (the VI lib leaves replay to the caller) + add a test. - [ ] **Gap (binding):** credentials-provider mints `pay_token_*` after verifying the payment chain, but the token isn't cryptographically bound to `checkout_jwt_hash`, and `complete_checkout` / the PSP don't re-verify the chain — a minted token could be presented for a different settlement. Bind the token to the checkout hash (and/or have the PSP re-verify) + add a test. +- [ ] **Gap (enforcement asymmetry, found iter 10):** `verifyCheckoutChain` (merchant) only verifies chain structure + aud — it does NOT run `checkConstraints` on the checkout-side constraints (`allowed_merchants`, `line_items`/`acceptable_items`); only `verifyPaymentChainAndConstraints` (network) enforces constraints (amount/payee). So the merchant trusts the agent's self-enforcement in `createAgentFulfillment` that the checked-out item is acceptable. Fix: enforce checkout constraints in the merchant path — decode the L3b `checkout_jwt` cart, resolve `acceptable_items` SD-refs, run `checkConstraints` (line_items max-flow). Needs careful design + e2e validation. - [ ] `verifyPaymentChainAndConstraints`: surface a clear error when the issuer key is missing, and test it. ## Docs