From f82a190a6aab1b93bc5dc0a572a887e9c6a48772 Mon Sep 17 00:00:00 2001 From: shivammittal274 Date: Tue, 25 Nov 2025 23:30:09 +0530 Subject: [PATCH 1/7] vercel ai adpater for gemini cli --- bun.lock | 691 ++++++++++++++--- package.json | 2 + packages/agent/package.json | 13 +- .../agent/gemini-vercel-sdk-adapter/errors.ts | 68 ++ .../agent/gemini-vercel-sdk-adapter/index.ts | 297 ++++++++ .../strategies/index.ts | 14 + .../strategies/message.test.ts | 706 ++++++++++++++++++ .../strategies/message.ts | 283 +++++++ .../strategies/response.test.ts | 550 ++++++++++++++ .../strategies/response.ts | 341 +++++++++ .../strategies/tool.test.ts | 582 +++++++++++++++ .../strategies/tool.ts | 225 ++++++ .../agent/gemini-vercel-sdk-adapter/types.ts | 239 ++++++ .../gemini-vercel-sdk-adapter/utils/index.ts | 19 + .../utils/type-guards.ts | 74 ++ 15 files changed, 4014 insertions(+), 90 deletions(-) create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/errors.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/index.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.test.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.test.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.test.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/utils/index.ts create mode 100644 packages/agent/src/agent/gemini-vercel-sdk-adapter/utils/type-guards.ts diff --git a/bun.lock b/bun.lock index 07a7dff..029b72b 100644 --- a/bun.lock +++ b/bun.lock @@ -8,9 +8,11 @@ "commander": "^14.0.1", "core-js": "3.45.1", "debug": "4.4.3", + "hono": "^4.10.6", "mitt": "^3.0.1", "proxy-agent": "^6.5.0", "puppeteer-core": "24.23.0", + "semver": "^7.7.3", "smol-toml": "^1.4.2", }, "devDependencies": { @@ -58,10 +60,19 @@ "name": "@browseros/agent", "version": "0.1.0", "dependencies": { + "@ai-sdk/amazon-bedrock": "^3.0.59", + "@ai-sdk/anthropic": "^2.0.47", + "@ai-sdk/azure": "^2.0.74", + "@ai-sdk/google": "^2.0.43", + "@ai-sdk/openai": "^2.0.72", + "@ai-sdk/openai-compatible": "^1.0.27", "@anthropic-ai/claude-agent-sdk": "^0.1.11", "@browseros/common": "workspace:*", "@browseros/server": "workspace:*", "@browseros/tools": "workspace:*", + "@google/gemini-cli-core": "^0.16.0", + "@openrouter/ai-sdk-provider": "~1.2.5", + "ai": "^5.0.101", "zod": "^4.1.12", }, "devDependencies": { @@ -195,8 +206,32 @@ }, }, "packages": { + "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.59", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.47", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-H5S4sh8nMd0xyMLi8BrMj3MHaduv6N4scisyZC/dUOk7A/hNp2/eZA9WXXLnOQN0kccbXx7H1i6ahS5cigjVXg=="], + + "@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YioBDTTQ6z2fijcOByG6Gj7me0ITqaJACprHROis7fXFzYIBzyAwxhsCnOrXO+oXv+9Ixddgy/Cahdmu84uRvQ=="], + + "@ai-sdk/azure": ["@ai-sdk/azure@2.0.74", "", { "dependencies": { "@ai-sdk/openai": "2.0.72", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0xmtnkrkONyskkbIRXi5hQ+23QUeSjBNiZSGlQ3SydCktUQo1ziyao0AQRwj/tx3z4+RhoQtpDT2nVAr2sknDA=="], + + "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.15", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-i1YVKzC1dg9LGvt+GthhD7NlRhz9J4+ZRj3KELU14IZ/MHPsOBiFeEoCCIDLR+3tqT8/+5nIsK3eZ7DFRfMfdw=="], + + "@ai-sdk/google": ["@ai-sdk/google@2.0.43", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qO6giuoYCX/SdZScP/3VO5Xnbd392zm3HrTkhab/efocZU8J/VVEAcAUE1KJh0qOIAYllofRtpJIUGkRK8Q5rw=="], + + "@ai-sdk/openai": ["@ai-sdk/openai@2.0.72", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9j8Gdt9gFiUGFdQIjjynbC7+w8YQxkXje6dwAq1v2Pj17wmB3U0Td3lnEe/a+EnEysY3mdkc8dHPYc5BNev9NQ=="], + + "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bpYruxVLhrTbVH6CCq48zMJNeHu6FmHtEedl9FXckEgcIEAi036idFhJlcRwC1jNCwlacbzb8dPD7OAH1EKJaQ=="], + + "@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], + + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.1.23", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^3.24.1" } }, "sha512-DktXOjzS2hOuuj2Zpo7fEooONfMa5bm09pt1/Vt4vn30YugELoezn/ZQ/TG5uSQ7+Zl/ElxAvi4vGDorj1Tirg=="], + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], @@ -361,6 +396,32 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0", "levn": "^0.4.1" } }, "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A=="], + "@google-cloud/common": ["@google-cloud/common@5.0.2", "", { "dependencies": { "@google-cloud/projectify": "^4.0.0", "@google-cloud/promisify": "^4.0.0", "arrify": "^2.0.1", "duplexify": "^4.1.1", "extend": "^3.0.2", "google-auth-library": "^9.0.0", "html-entities": "^2.5.2", "retry-request": "^7.0.0", "teeny-request": "^9.0.0" } }, "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA=="], + + "@google-cloud/logging": ["@google-cloud/logging@11.2.1", "", { "dependencies": { "@google-cloud/common": "^5.0.0", "@google-cloud/paginator": "^5.0.0", "@google-cloud/projectify": "^4.0.0", "@google-cloud/promisify": "4.0.0", "@opentelemetry/api": "^1.7.0", "arrify": "^2.0.1", "dot-prop": "^6.0.0", "eventid": "^2.0.0", "extend": "^3.0.2", "gcp-metadata": "^6.0.0", "google-auth-library": "^9.0.0", "google-gax": "^4.0.3", "on-finished": "^2.3.0", "pumpify": "^2.0.1", "stream-events": "^1.0.5", "uuid": "^9.0.0" } }, "sha512-2h9HBJG3OAsvzXmb81qXmaTPfXYU7KJTQUxunoOKFGnY293YQ/eCkW1Y5mHLocwpEqeqQYT/Qvl6Tk+Q7PfStw=="], + + "@google-cloud/opentelemetry-cloud-monitoring-exporter": ["@google-cloud/opentelemetry-cloud-monitoring-exporter@0.21.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" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-metrics": "^2.0.0" } }, "sha512-+lAew44pWt6rA4l8dQ1gGhH7Uo95wZKfq/GBf9aEyuNDDLQ2XppGEEReu6ujesSqTtZ8ueQFt73+7SReSHbwqg=="], + + "@google-cloud/opentelemetry-cloud-trace-exporter": ["@google-cloud/opentelemetry-cloud-trace-exporter@3.0.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" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0" } }, "sha512-mUfLJBFo+ESbO0dAGboErx2VyZ7rbrHcQvTP99yH/J72dGaPbH2IzS+04TFbTbEd1VW5R9uK3xq2CqawQaG+1Q=="], + + "@google-cloud/opentelemetry-resource-util": ["@google-cloud/opentelemetry-resource-util@3.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.22.0", "gcp-metadata": "^6.0.0" }, "peerDependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0" } }, "sha512-CGR/lNzIfTKlZoZFfS6CkVzx+nsC9gzy6S8VcyaLegfEJbiPjxbMLP7csyhJTvZe/iRRcQJxSk0q8gfrGqD3/Q=="], + + "@google-cloud/paginator": ["@google-cloud/paginator@5.0.2", "", { "dependencies": { "arrify": "^2.0.0", "extend": "^3.0.2" } }, "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg=="], + + "@google-cloud/precise-date": ["@google-cloud/precise-date@4.0.0", "", {}, "sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA=="], + + "@google-cloud/projectify": ["@google-cloud/projectify@4.0.0", "", {}, "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA=="], + + "@google-cloud/promisify": ["@google-cloud/promisify@4.0.0", "", {}, "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g=="], + + "@google/gemini-cli-core": ["@google/gemini-cli-core@0.16.0", "", { "dependencies": { "@google-cloud/logging": "^11.2.1", "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", "@google/genai": "1.16.0", "@iarna/toml": "^2.2.5", "@joshua.litt/get-ripgrep": "^0.0.3", "@modelcontextprotocol/sdk": "^1.11.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.203.0", "@opentelemetry/exporter-logs-otlp-http": "^0.203.0", "@opentelemetry/exporter-metrics-otlp-grpc": "^0.203.0", "@opentelemetry/exporter-metrics-otlp-http": "^0.203.0", "@opentelemetry/exporter-trace-otlp-grpc": "^0.203.0", "@opentelemetry/exporter-trace-otlp-http": "^0.203.0", "@opentelemetry/instrumentation-http": "^0.203.0", "@opentelemetry/resource-detector-gcp": "^0.40.0", "@opentelemetry/sdk-node": "^0.203.0", "@types/glob": "^8.1.0", "@types/html-to-text": "^9.0.4", "@xterm/headless": "5.5.0", "ajv": "^8.17.1", "ajv-formats": "^3.0.0", "chardet": "^2.1.0", "diff": "^7.0.0", "dotenv": "^17.1.0", "fast-levenshtein": "^2.0.6", "fast-uri": "^3.0.6", "fdir": "^6.4.6", "fzf": "^0.5.2", "glob": "^10.4.5", "google-auth-library": "^9.11.0", "html-to-text": "^9.0.5", "https-proxy-agent": "^7.0.6", "ignore": "^7.0.0", "marked": "^15.0.12", "mime": "4.0.7", "mnemonist": "^0.40.3", "open": "^10.1.2", "picomatch": "^4.0.1", "read-package-up": "^11.0.0", "shell-quote": "^1.8.3", "simple-git": "^3.28.0", "strip-ansi": "^7.1.0", "tree-sitter-bash": "^0.25.0", "undici": "^7.10.0", "web-tree-sitter": "^0.25.10", "ws": "^8.18.0", "zod": "^3.25.76" }, "optionalDependencies": { "@lydell/node-pty": "1.1.0", "@lydell/node-pty-darwin-arm64": "1.1.0", "@lydell/node-pty-darwin-x64": "1.1.0", "@lydell/node-pty-linux-x64": "1.1.0", "@lydell/node-pty-win32-arm64": "1.1.0", "@lydell/node-pty-win32-x64": "1.1.0", "node-pty": "^1.0.0" } }, "sha512-EYzcAUcIcfkLJQGHabS96Y47A9ofEapzgJwLtbzpUwYFBuAegQcnl3xhbdxfj6kCygVHq2rPoa/udEVfqryOjQ=="], + + "@google/genai": ["@google/genai@1.16.0", "", { "dependencies": { "google-auth-library": "^9.14.2", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.11.4" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-hdTYu39QgDFxv+FB6BK2zi4UIJGWhx2iPc0pHQ0C5Q/RCi+m+4gsryIzTGO+riqWcUA8/WGYp6hpqckdOBNysw=="], + + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.1", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-sPxgEWtPUR3EnRJCEtbGZG2iX8LQDUls2wUS3o27jg07KqJFMq6YDeWvMo1wfpmy3rqRdS0rivpLwhqQtEyCuQ=="], + + "@grpc/proto-loader": ["@grpc/proto-loader@0.8.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" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], @@ -369,6 +430,8 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="], + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], @@ -429,6 +492,8 @@ "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="], + "@joshua.litt/get-ripgrep": ["@joshua.litt/get-ripgrep@0.0.3", "", { "dependencies": { "@lvce-editor/verror": "^1.6.0", "execa": "^9.5.2", "extract-zip": "^2.0.1", "fs-extra": "^11.3.0", "got": "^14.4.5", "path-exists": "^5.0.0", "xdg-basedir": "^5.1.0" } }, "sha512-rycdieAKKqXi2bsM7G2ayDiNk5CAX8ZOzsTQsirfOqUKPef04Xw40BWGGyimaOOuvPgLWYt3tPnLLG3TvPXi5Q=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -441,6 +506,30 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], + + "@keyv/serialize": ["@keyv/serialize@1.1.1", "", {}, "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA=="], + + "@kwsites/file-exists": ["@kwsites/file-exists@1.1.1", "", { "dependencies": { "debug": "^4.1.1" } }, "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw=="], + + "@kwsites/promise-deferred": ["@kwsites/promise-deferred@1.1.1", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="], + + "@lvce-editor/verror": ["@lvce-editor/verror@1.7.0", "", {}, "sha512-+LGuAEIC2L7pbvkyAQVWM2Go0dAy+UWEui28g07zNtZsCBhm+gusBK8PNwLJLV5Jay+TyUYuwLIbJdjLLzqEBg=="], + + "@lydell/node-pty": ["@lydell/node-pty@1.1.0", "", { "optionalDependencies": { "@lydell/node-pty-darwin-arm64": "1.1.0", "@lydell/node-pty-darwin-x64": "1.1.0", "@lydell/node-pty-linux-arm64": "1.1.0", "@lydell/node-pty-linux-x64": "1.1.0", "@lydell/node-pty-win32-arm64": "1.1.0", "@lydell/node-pty-win32-x64": "1.1.0" } }, "sha512-VDD8LtlMTOrPKWMXUAcB9+LTktzuunqrMwkYR1DMRBkS6LQrCt+0/Ws1o2rMml/n3guePpS7cxhHF7Nm5K4iMw=="], + + "@lydell/node-pty-darwin-arm64": ["@lydell/node-pty-darwin-arm64@1.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7kFD+owAA61qmhJCtoMbqj3Uvff3YHDiU+4on5F2vQdcMI3MuwGi7dM6MkFG/yuzpw8LF2xULpL71tOPUfxs0w=="], + + "@lydell/node-pty-darwin-x64": ["@lydell/node-pty-darwin-x64@1.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-XZdvqj5FjAMjH8bdp0YfaZjur5DrCIDD1VYiE9EkkYVMDQqRUPHYV3U8BVEQVT9hYfjmpr7dNaELF2KyISWSNA=="], + + "@lydell/node-pty-linux-arm64": ["@lydell/node-pty-linux-arm64@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yyDBmalCfHpLiQMT2zyLcqL2Fay4Xy7rIs8GH4dqKLnEviMvPGOK7LADVkKAsbsyXBSISL3Lt1m1MtxhPH6ckg=="], + + "@lydell/node-pty-linux-x64": ["@lydell/node-pty-linux-x64@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-NcNqRTD14QT+vXcEuqSSvmWY+0+WUBn2uRE8EN0zKtDpIEr9d+YiFj16Uqds6QfcLCHfZmC+Ls7YzwTaqDnanA=="], + + "@lydell/node-pty-win32-arm64": ["@lydell/node-pty-win32-arm64@1.1.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-JOMbCou+0fA7d/m97faIIfIU0jOv8sn2OR7tI45u3AmldKoKoLP8zHY6SAvDDnI3fccO1R2HeR1doVjpS7HM0w=="], + + "@lydell/node-pty-win32-x64": ["@lydell/node-pty-win32-x64@1.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-3N56BZ+WDFnUMYRtsrr7Ky2mhWGl9xXcyqR6cexfuCqcz9RNWL+KoXRv/nZylY5dYaXkft4JaR1uVu+roiZDAw=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.20.0", "", { "dependencies": { "ajv": "^6.12.6", "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.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-kOQ4+fHuT4KbR2iq2IjeV32HiihueuOf1vJkq18z08CLZ1UQrTc8BXJpVfxZkq45+inLLD+D4xx4nBjUelJa4Q=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], @@ -451,8 +540,92 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.2.5", "", { "dependencies": { "@openrouter/sdk": "^0.1.8" }, "peerDependencies": { "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" } }, "sha512-NrvJFPvdEUo6DYUQIVWPGfhafuZ2PAIX7+CUMKGknv8TcTNVo0TyP1y5SU7Bgjf/Wup9/74UFKUB07icOhVZjQ=="], + + "@openrouter/sdk": ["@openrouter/sdk@0.1.27", "", { "dependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-RH//L10bSmc81q25zAZudiI4kNkLgxF2E+WU42vghp3N6TEvZ6F0jK7uT3tOxkEn91gzmMw9YVmDENy7SJsajQ=="], + + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.203.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ=="], + + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.0.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw=="], + + "@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="], + + "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.203.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/sdk-logs": "0.203.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-g/2Y2noc/l96zmM+g0LdeuyYKINyBwN6FJySoU15LHPLcMN/1a0wNk2SegwKcxrRdE7Xsm7fkIR5n6XFe3QpPw=="], + + "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/sdk-logs": "0.203.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-s0hys1ljqlMTbXx2XiplmMJg9wG570Z5lH7wMvrZX6lcODI56sG4HL03jklF63tBeyNwK2RV1/ntXGo3HgG4Qw=="], + + "@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.203.0", "@opentelemetry/sdk-trace-base": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-nl/7S91MXn5R1aIzoWtMKGvqxgJgepB/sH9qW0rZvZtabnsjbf8OQ1uSx3yogtvLr0GzwD596nQKz2fV7q2RBw=="], + + "@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.203.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.1", "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-metrics": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FCCj9nVZpumPQSEI57jRAA89hQQgONuoC35Lt+rayWY/mzCAc6BQT7RFyFaZKJ2B7IQ8kYjOCPsF/HGFWjdQkQ=="], + + "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-metrics": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HFSW10y8lY6BTZecGNpV3GpoSy7eaO0Z6GATwZasnT4bEsILp8UJXNG5OmEsz4SdwCSYvyCbTJdNbZP3/8LGCQ=="], + + "@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-metrics": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-OZnhyd9npU7QbyuHXFEPVm3LnjZYifuKpT3kTnF84mXeEQ84pJJZgyLBpU4FSkSwUkt/zbMyNAI7y5+jYTWGIg=="], + + "@opentelemetry/exporter-prometheus": ["@opentelemetry/exporter-prometheus@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-metrics": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-2jLuNuw5m4sUj/SncDf/mFPabUxMZmmYetx5RKIMIQyPnl6G6ooFzfeE8aXNRf8YD1ZXNlCnRPcISxjveGJHNg=="], + + "@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.203.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-322coOTf81bm6cAA8+ML6A+m4r2xTCdmAZzGNTboPXRzhwPt4JEmovsFAs+grpdarObd68msOJ9FfH3jxM6wqA=="], + + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ZDiaswNYo0yq/cy1bBLJFe691izEJ6IgNmkjm4C6kE9ub/OMQqDXORx2D2j8fzTBTxONyzusbaZlqtfmyqURPw=="], + + "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-1xwNTJ86L0aJmWRwENCJlH4LULMG2sOXWIVw+Szta4fkqKVY50Eo4HoVKKq6U9QEytrWCr8+zjw0q/ZOeXpcAQ=="], + + "@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-a9eeyHIipfdxzCfc2XPrE+/TI3wmrZUDFtG2RRXHSbZZULAny7SyybSvaDvS77a7iib5MPiAvluwVvbGTsHxsw=="], + + "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ=="], + + "@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/instrumentation": "0.203.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y3uQAcCOAwnO6vEuNVocmpVzG3PER6/YZqbPbbffDdJ9te5NkHEkfSMNzlC3+v7KlE+WinPGc3N7MR30G1HY2g=="], + + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-transformer": "0.203.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ=="], + + "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.203.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-te0Ze1ueJF+N/UOFl5jElJW4U0pZXQ8QklgSfJ2linHN0JJsuaHG8IabEUi2iqxY8ZBDlSiz1Trfv5JcjWWWwQ=="], + + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.203.0", "@opentelemetry/sdk-metrics": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Y8I6GgoCna0qDQ2W6GCRtaF24SnvqvA8OfeTi7fqigD23u8Jpb4R5KFv/pRvrlGagcCLICMIyh9wiejp4TXu/A=="], + + "@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Hc09CaQ8Tf5AGLmf449H726uRoBNGPBL4bjr7AnnUpzWMvhdn61F78z9qb6IqB737TffBsokGAK1XykFEZ1igw=="], + + "@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-7PMdPBmGVH2eQNb/AtSJizQNgeNTfh6jQFqys6lfhd6P4r+m/nTh3gKPPpaCXVdRQ+z93vfKk+4UGty390283w=="], + + "@opentelemetry/resource-detector-gcp": ["@opentelemetry/resource-detector-gcp@0.40.3", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "gcp-metadata": "^6.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-C796YjBA5P1JQldovApYfFA/8bQwFfpxjUbOtGhn1YZkVTLoNQN+kvBwgALfTPWzug6fWsd0xhn9dzeiUcndag=="], + + "@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="], + + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-vM2+rPq0Vi3nYA5akQD2f3QwossDnTDLvKbea6u/A2NZ3XDkPxMfo/PNrDoXhDUD/0pPo2CdH5ce/thn9K0kLw=="], + + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="], + + "@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/exporter-logs-otlp-grpc": "0.203.0", "@opentelemetry/exporter-logs-otlp-http": "0.203.0", "@opentelemetry/exporter-logs-otlp-proto": "0.203.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.203.0", "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.203.0", "@opentelemetry/exporter-prometheus": "0.203.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.203.0", "@opentelemetry/exporter-trace-otlp-http": "0.203.0", "@opentelemetry/exporter-trace-otlp-proto": "0.203.0", "@opentelemetry/exporter-zipkin": "2.0.1", "@opentelemetry/instrumentation": "0.203.0", "@opentelemetry/propagator-b3": "2.0.1", "@opentelemetry/propagator-jaeger": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.203.0", "@opentelemetry/sdk-metrics": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1", "@opentelemetry/sdk-trace-node": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-zRMvrZGhGVMvAbbjiNQW3eKzW/073dlrSiAKPVWmkoQzah9wfynpVPeL55f9fVIm0GaBxTLcPeukWGy0/Wj7KQ=="], + + "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ=="], + + "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.0.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.0.1", "@opentelemetry/core": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-UhdbPF19pMpBtCWYP5lHbTogLWx9N0EBxtdagvkn5YtsAnCBZzL7SjktG+ZmupRgifsHMjwUaCCaVmqGfSADmA=="], + + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.38.0", "", {}, "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + "@puppeteer/browsers": ["@puppeteer/browsers@2.10.10", "", { "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.2", "tar-fs": "^3.1.0", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-3ZG500+ZeLql8rE0hjfhkycJjDj0pI/btEh3L9IkWUYcOrgP0xCNRq3HbtbqOPbvDhFaAWD88pDFtlLv8ns8gA=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.5", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="], @@ -501,8 +674,14 @@ "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + + "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], + "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + "@sindresorhus/is": ["@sindresorhus/is@7.1.1", "", {}, "sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ=="], + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="], "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], @@ -511,8 +690,24 @@ "@sinonjs/samsam": ["@sinonjs/samsam@8.0.3", "", { "dependencies": { "@sinonjs/commons": "^3.0.1", "type-detect": "^4.1.0" } }, "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ=="], + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@stylistic/eslint-plugin": ["@stylistic/eslint-plugin@5.5.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.0", "@typescript-eslint/types": "^8.46.1", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-IeZF+8H0ns6prg4VrkhgL+yrvDXWDH2cKchrbh80ejG9dQgZWp10epHMbgRuQvgchLII/lfh6Xn3lu6+6L86Hw=="], + "@tootallnate/once": ["@tootallnate/once@2.0.0", "", {}, "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="], + "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], "@tsconfig/node10": ["@tsconfig/node10@1.0.11", "", {}, "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw=="], @@ -535,6 +730,8 @@ "@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="], + "@types/caseless": ["@types/caseless@0.12.5", "", {}, "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg=="], + "@types/chrome": ["@types/chrome@0.1.24", "", { "dependencies": { "@types/filesystem": "*", "@types/har-format": "*" } }, "sha512-9iO9HL2bMeGS4C8m6gNFWUyuPE5HEUFk+rGh+7oriUjg+ata4Fc9PoVlu8xvGm7yoo3AmS3J6fAjoFj61NL2rw=="], "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], @@ -549,10 +746,16 @@ "@types/filewriter": ["@types/filewriter@0.0.33", "", {}, "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g=="], + "@types/glob": ["@types/glob@8.1.0", "", { "dependencies": { "@types/minimatch": "^5.1.2", "@types/node": "*" } }, "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w=="], + "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="], "@types/har-format": ["@types/har-format@1.2.16", "", {}, "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A=="], + "@types/html-to-text": ["@types/html-to-text@9.0.4", "", {}, "sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ=="], + + "@types/http-cache-semantics": ["@types/http-cache-semantics@4.0.4", "", {}, "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA=="], + "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], @@ -565,18 +768,28 @@ "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], + "@types/long": ["@types/long@4.0.2", "", {}, "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="], + + "@types/minimatch": ["@types/minimatch@5.1.2", "", {}, "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA=="], + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], "@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], + "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], + "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + "@types/request": ["@types/request@2.48.13", "", { "dependencies": { "@types/caseless": "*", "@types/node": "*", "@types/tough-cookie": "*", "form-data": "^2.5.5" } }, "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg=="], + "@types/sinon": ["@types/sinon@17.0.4", "", { "dependencies": { "@types/sinonjs__fake-timers": "*" } }, "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew=="], "@types/sinonjs__fake-timers": ["@types/sinonjs__fake-timers@8.1.5", "", {}, "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ=="], "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], + "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="], + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], "@types/yargs": ["@types/yargs@17.0.34", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A=="], @@ -643,6 +856,8 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], + "@vercel/oidc": ["@vercel/oidc@3.0.5", "", {}, "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw=="], + "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="], @@ -679,14 +894,20 @@ "@webpack-cli/serve": ["@webpack-cli/serve@3.0.1", "", { "peerDependencies": { "webpack": "^5.82.0", "webpack-cli": "6.x.x" } }, "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg=="], + "@xterm/headless": ["@xterm/headless@5.5.0", "", {}, "sha512-5xXB7kdQlFBP82ViMJTwwEc3gKCLGKR/eoxQm4zge7GPBl86tCdI0IdPJjoKd8mUSFXz5V7i/25sfsEkP4j46g=="], + "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], + "acorn-import-phases": ["acorn-import-phases@1.0.4", "", { "peerDependencies": { "acorn": "^8.14.0" } }, "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -695,15 +916,17 @@ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ai": ["ai@5.0.101", "", { "dependencies": { "@ai-sdk/gateway": "2.0.15", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/P4fgs2PGYTBaZi192YkPikOudsl9vccA65F7J7LvoNTOoP5kh1yAsJPsKAy6FXU32bAngai7ft1UDyC3u7z5g=="], + "ajv": ["ajv@6.12.6", "", { "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" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], - "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], "ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="], "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], @@ -727,14 +950,20 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "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" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + "arrify": ["arrify@2.0.1", "", {}, "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="], + "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], "async-mutex": ["async-mutex@0.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], + "b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="], "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="], @@ -761,10 +990,14 @@ "bare-url": ["bare-url@2.3.1", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-v2yl0TnaZTdEnelkKtXZGnotiV6qATBlnNuUMrHl6v9Lmmrh9mw9RYyImPU7/4RahumSwQS1k2oKXcRfXcbjJw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-zoKGUdu6vb2jd3YOq0nnhEDQVbPcHhco3UImJrv5dSkvxTc2pl2WjOPsjZXDwPDSl5eghIMuY3R6J9NDKF3KcQ=="], "basic-ftp": ["basic-ftp@5.0.5", "", {}, "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg=="], + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], @@ -781,16 +1014,26 @@ "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="], + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + "byte-counter": ["byte-counter@0.1.0", "", {}, "sha512-jheRLVMeUKrDBjVw2O5+k4EvR4t9wtxHL+bo/LxfkxsVeuGMy3a5SEGgXdAFA4FSzTrU8rQXQIrsZ3oBq5a0pQ=="], + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "cacheable-lookup": ["cacheable-lookup@7.0.0", "", {}, "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="], + + "cacheable-request": ["cacheable-request@13.0.15", "", { "dependencies": { "@types/http-cache-semantics": "^4.0.4", "get-stream": "^9.0.1", "http-cache-semantics": "^4.2.0", "keyv": "^5.5.4", "mimic-response": "^4.0.0", "normalize-url": "^8.1.0", "responselike": "^4.0.2" } }, "sha512-NjiSrjv37X73FmGGU5ec/M83vWQ6q1Ae3BFe+ABfdeeMy4LOMKYTpfEjrBnLedu43clKZtsYbKrHTIQE7vKq+A=="], + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -807,11 +1050,13 @@ "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], + "chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="], + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "chrome-devtools-frontend": ["chrome-devtools-frontend@1.0.1524741", "", {}, "sha512-F2K56RgHeF+8JvQIcIm6GyWNEOqql0eeKwIXLziS//LPBy7/7I6zCko/poRU07U3xlIajhjkZO3dSuimn3fg8Q=="], - "chrome-devtools-mcp": ["chrome-devtools-mcp@0.8.1", "", { "dependencies": { "@modelcontextprotocol/sdk": "1.20.0", "core-js": "3.46.0", "debug": "4.4.3", "puppeteer-core": "^24.24.1", "yargs": "18.0.0", "zod": "^3.25.76" }, "bin": { "chrome-devtools-mcp": "build/src/index.js" } }, "sha512-KaLoeUHtbMsq4+p19tHd6y78nO9r+hUxQYPttJnWKu6gvTAazKMqpvEe3kzKOOGEY4vWQs7oacpDHyT9KcT2tg=="], + "chrome-devtools-mcp": ["chrome-devtools-mcp@0.10.2", "", { "bin": { "chrome-devtools-mcp": "build/src/index.js" } }, "sha512-GvwA9Ity2tS1peVvZXTtl6DmpAPWPjKA451nb9qn9+re/v7IcchcmVIdTdOy3hCrkTbglNwPj/wwGPZ9x2IYvQ=="], "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], @@ -821,7 +1066,7 @@ "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], - "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "clone-deep": ["clone-deep@4.0.1", "", { "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", "shallow-clone": "^3.0.0" } }, "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ=="], @@ -835,6 +1080,8 @@ "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + "commander": ["commander@14.0.1", "", {}, "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], @@ -879,18 +1126,28 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "decompress-response": ["decompress-response@10.0.0", "", { "dependencies": { "mimic-response": "^4.0.0" } }, "sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q=="], + "dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], @@ -903,19 +1160,33 @@ "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + + "dot-prop": ["dot-prop@6.0.1", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA=="], + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "duplexify": ["duplexify@4.1.3", "", { "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", "stream-shift": "^1.0.2" } }, "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA=="], + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], "electron-to-chromium": ["electron-to-chromium@1.5.237", "", {}, "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg=="], "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="], - "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -923,6 +1194,8 @@ "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], "envinfo": ["envinfo@7.19.0", "", { "bin": { "envinfo": "dist/cli.js" } }, "sha512-DoSM9VyG6O3vqBf+p3Gjgr/Q52HYBBtO3v+4koAxt1MnWr+zEnxE+nke/yXS4lt2P4SYCHQ4V3f1i88LQVOpAw=="], @@ -991,6 +1264,10 @@ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + + "eventid": ["eventid@2.0.1", "", { "dependencies": { "uuid": "^8.0.0" } }, "sha512-sPNTqiMokAvV048P2c9+foqVJzk49o6d4e0D/sq5jog3pw+4kBgyR0gaM1FM7Mx6Kzd9dztesh9oYz1LWWOpzw=="], + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], @@ -999,7 +1276,7 @@ "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], - "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + "execa": ["execa@9.6.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw=="], "exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="], @@ -1009,6 +1286,8 @@ "express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -1033,6 +1312,8 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -1041,6 +1322,8 @@ "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + "find-up-simple": ["find-up-simple@1.0.1", "", {}, "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ=="], + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], "flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="], @@ -1053,10 +1336,18 @@ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + "form-data": ["form-data@2.5.5", "", { "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" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="], + + "form-data-encoder": ["form-data-encoder@4.1.0", "", {}, "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw=="], + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -1067,14 +1358,18 @@ "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + "fzf": ["fzf@0.5.2", "", {}, "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q=="], + + "gaxios": ["gaxios@6.7.1", "", { "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" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="], + + "gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="], + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="], @@ -1101,12 +1396,26 @@ "globby": ["globby@14.1.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.3", "ignore": "^7.0.3", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA=="], + "google-auth-library": ["google-auth-library@9.15.1", "", { "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" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="], + + "google-gax": ["google-gax@4.6.1", "", { "dependencies": { "@grpc/grpc-js": "^1.10.9", "@grpc/proto-loader": "^0.7.13", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "google-auth-library": "^9.3.0", "node-fetch": "^2.7.0", "object-hash": "^3.0.0", "proto3-json-serializer": "^2.0.2", "protobufjs": "^7.3.2", "retry-request": "^7.0.0", "uuid": "^9.0.1" } }, "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ=="], + + "google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], + + "googleapis": ["googleapis@137.1.0", "", { "dependencies": { "google-auth-library": "^9.0.0", "googleapis-common": "^7.0.0" } }, "sha512-2L7SzN0FLHyQtFmyIxrcXhgust77067pkkduqkbIpDuj9JzVnByxsRrcRfUMFQam3rQkWW2B0f1i40IwKDWIVQ=="], + + "googleapis-common": ["googleapis-common@7.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" } }, "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "got": ["got@14.6.4", "", { "dependencies": { "@sindresorhus/is": "^7.0.1", "byte-counter": "^0.1.0", "cacheable-lookup": "^7.0.0", "cacheable-request": "^13.0.12", "decompress-response": "^10.0.0", "form-data-encoder": "^4.0.2", "http2-wrapper": "^2.2.1", "keyv": "^5.5.3", "lowercase-keys": "^3.0.0", "p-cancelable": "^4.0.1", "responselike": "^4.0.2", "type-fest": "^4.26.1" } }, "sha512-DjsLab39NUMf5iYlK9asVCkHMhaA2hEhrlmf+qXRhjEivuuBHWYbjmty9DA3OORUwZgENTB+6vSmY2ZW8gFHVw=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + "gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="], + "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], @@ -1123,15 +1432,29 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hono": ["hono@4.10.6", "", {}, "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g=="], + + "hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], + + "html-entities": ["html-entities@2.6.0", "", {}, "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="], + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + "html-to-text": ["html-to-text@9.0.5", "", { "dependencies": { "@selderee/plugin-htmlparser2": "^0.11.0", "deepmerge": "^4.3.1", "dom-serializer": "^2.0.0", "htmlparser2": "^8.0.2", "selderee": "^0.11.0" } }, "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg=="], + + "htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + "http2-wrapper": ["http2-wrapper@2.2.1", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" } }, "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], "iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], @@ -1139,10 +1462,14 @@ "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "import-in-the-middle": ["import-in-the-middle@1.15.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" } }, "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA=="], + "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], @@ -1175,6 +1502,8 @@ "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], @@ -1187,6 +1516,8 @@ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], @@ -1195,6 +1526,10 @@ "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + "is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + "is-plain-object": ["is-plain-object@2.0.4", "", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="], "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], @@ -1205,7 +1540,7 @@ "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], - "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], @@ -1213,12 +1548,16 @@ "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], @@ -1297,22 +1636,34 @@ "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], @@ -1327,12 +1678,18 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="], + "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -1343,6 +1700,8 @@ "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="], + "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], @@ -1355,12 +1714,16 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + "mime": ["mime@4.0.7", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ=="], + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="], + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], @@ -1371,10 +1734,18 @@ "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + "mnemonist": ["mnemonist@0.40.3", "", { "dependencies": { "obliterator": "^2.0.4" } }, "sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ=="], + + "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + "nan": ["nan@2.23.1", "", {}, "sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], @@ -1385,16 +1756,30 @@ "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], + "node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], + "node-pty": ["node-pty@1.0.0", "", { "dependencies": { "nan": "^2.17.0" } }, "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA=="], + "node-releases": ["node-releases@2.0.26", "", {}, "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA=="], + "normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="], + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], - "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + "normalize-url": ["normalize-url@8.1.0", "", {}, "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w=="], + + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], @@ -1407,16 +1792,22 @@ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], + "obliterator": ["obliterator@2.0.5", "", {}, "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw=="], + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "optionator": ["optionator@0.9.4", "", { "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" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], + "p-cancelable": ["p-cancelable@4.0.1", "", {}, "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], @@ -1433,6 +1824,10 @@ "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + + "parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="], + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], @@ -1451,6 +1846,8 @@ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="], + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -1467,6 +1864,8 @@ "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], @@ -1475,10 +1874,16 @@ "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + "proto3-json-serializer": ["proto3-json-serializer@2.0.2", "", { "dependencies": { "protobufjs": "^7.2.5" } }, "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ=="], + + "protobufjs": ["protobufjs@7.5.4", "", { "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" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], @@ -1487,6 +1892,8 @@ "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], + "pumpify": ["pumpify@2.0.1", "", { "dependencies": { "duplexify": "^4.1.1", "inherits": "^2.0.3", "pump": "^3.0.0" } }, "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "puppeteer": ["puppeteer@24.23.0", "", { "dependencies": { "@puppeteer/browsers": "2.10.10", "chromium-bidi": "9.1.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1508733", "puppeteer-core": "24.23.0", "typed-query-selector": "^2.12.0" }, "bin": { "puppeteer": "lib/cjs/puppeteer/node/cli.js" } }, "sha512-BVR1Lg8sJGKXY79JARdIssFWK2F6e1j+RyuJP66w4CUmpaXjENicmA3nNpUXA8lcTdDjAndtP+oNdni3T/qQqA=="], @@ -1499,6 +1906,8 @@ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], @@ -1507,6 +1916,12 @@ "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + "read-package-up": ["read-package-up@11.0.0", "", { "dependencies": { "find-up-simple": "^1.0.0", "read-pkg": "^9.0.0", "type-fest": "^4.6.0" } }, "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ=="], + + "read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "rechoir": ["rechoir@0.8.0", "", { "dependencies": { "resolve": "^1.20.0" } }, "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ=="], @@ -1519,8 +1934,12 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="], + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], + "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], @@ -1529,6 +1948,10 @@ "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="], + "responselike": ["responselike@4.0.2", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA=="], + + "retry-request": ["retry-request@7.0.2", "", { "dependencies": { "@types/request": "^2.48.8", "extend": "^3.0.2", "teeny-request": "^9.0.0" } }, "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "rimraf": ["rimraf@6.0.1", "", { "dependencies": { "glob": "^11.0.0", "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A=="], @@ -1537,6 +1960,8 @@ "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "safe-array-concat": ["safe-array-concat@1.1.3", "", { "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" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], @@ -1551,7 +1976,9 @@ "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], - "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="], + + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="], @@ -1573,6 +2000,8 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + "side-channel": ["side-channel@1.1.0", "", { "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" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], @@ -1583,6 +2012,8 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "simple-git": ["simple-git@3.30.0", "", { "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", "debug": "^4.4.0" } }, "sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg=="], + "sinon": ["sinon@21.0.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "^13.0.5", "@sinonjs/samsam": "^8.0.1", "diff": "^7.0.0", "supports-color": "^7.2.0" } }, "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], @@ -1599,8 +2030,18 @@ "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="], + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], "stable-hash-x": ["stable-hash-x@0.2.0", "", {}, "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ=="], @@ -1611,11 +2052,15 @@ "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], + "stream-events": ["stream-events@1.0.5", "", { "dependencies": { "stubs": "^3.0.0" } }, "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg=="], + + "stream-shift": ["stream-shift@1.0.3", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="], + "streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="], "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1625,16 +2070,20 @@ "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], - "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "stubs": ["stubs@3.0.0", "", {}, "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw=="], + "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -1647,6 +2096,8 @@ "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], + "teeny-request": ["teeny-request@9.0.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" } }, "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g=="], + "terser": ["terser@5.44.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w=="], "terser-webpack-plugin": ["terser-webpack-plugin@5.3.14", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw=="], @@ -1673,6 +2124,8 @@ "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + "tree-sitter-bash": ["tree-sitter-bash@0.25.0", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-gZtlj9+qFS81qKxpLfD6H0UssQ3QBc/F0nKkPsiFDyfQF2YBqYvglFJUzchrPpVhZe9kLZTrJ9n2J6lmka69Vg=="], + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], @@ -1691,6 +2144,8 @@ "tsup": ["tsup@8.5.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ=="], + "tsx": ["tsx@4.20.6", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="], @@ -1719,10 +2174,14 @@ "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + "undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "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" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], @@ -1731,16 +2190,26 @@ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "url-template": ["url-template@2.0.8", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + "v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="], "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], "watchpack": ["watchpack@2.4.4", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA=="], + "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="], + "webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.3.6", "", {}, "sha512-mlGndEOA9yK9YAbvtxaPTqdi/kaCWYYfwrZvGzcmkr/3lWM+tQj53BxtpVd6qbC6+E5OnHXgCcAhre6AkXzxjA=="], "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], @@ -1771,7 +2240,7 @@ "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], - "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -1781,11 +2250,17 @@ "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + + "xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], + "yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="], + + "yargs": ["yargs@17.7.2", "", { "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" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], @@ -1795,14 +2270,24 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="], + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + "@browseros/agent/@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], + "@browseros/agent/zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], "@browseros/codex-sdk-ts/@types/node": ["@types/node@20.19.23", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ=="], @@ -1813,6 +2298,8 @@ "@browseros/tools/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.19.1", "", { "dependencies": { "ajv": "^6.12.6", "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.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-3Y2h3MZKjec1eAqSTBclATlX+AbC6n1LgfVzRMJLt3v6w0RCYgwLrjbxPDbhsYHt6Wdqc/aCceNJYgj448ELQQ=="], + "@browseros/tools/@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], + "@browseros/tools/zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="], "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], @@ -1823,9 +2310,11 @@ "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + "@google/gemini-cli-core/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], - "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "@google/gemini-cli-core/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], @@ -1835,24 +2324,26 @@ "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], + "@jest/core/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@jest/fake-timers/@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="], "@jest/reporters/glob": ["glob@7.2.3", "", { "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" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "@jest/reporters/jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="], - "@modelcontextprotocol/sdk/zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="], + "@jest/reporters/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@joshua.litt/get-ripgrep/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], - "@puppeteer/browsers/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "@modelcontextprotocol/sdk/zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="], - "@puppeteer/browsers/yargs": ["yargs@17.7.2", "", { "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" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + "@openrouter/sdk/zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], "@sinonjs/samsam/type-detect": ["type-detect@4.1.0", "", {}, "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "accepts/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], "ajv-formats/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], @@ -1869,15 +2360,15 @@ "browseros-controller/zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], - "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "cacheable-request/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], - "chrome-devtools-mcp/core-js": ["core-js@3.46.0", "", {}, "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA=="], + "cacheable-request/keyv": ["keyv@5.5.4", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ=="], - "chrome-devtools-mcp/puppeteer-core": ["puppeteer-core@24.26.0", "", { "dependencies": { "@puppeteer/browsers": "2.10.12", "chromium-bidi": "10.5.1", "debug": "^4.4.3", "devtools-protocol": "0.0.1508733", "typed-query-selector": "^2.12.0", "webdriver-bidi-protocol": "0.3.8", "ws": "^8.18.3" } }, "sha512-l3aMYhTdSzazZ14rfpNAPGhnYHsd8mwduqybhu5aO/OR+d24P/V/eo8XTB3GB2yX2ZWf9GLAVcx8nnVPFZpP/A=="], + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "chromium-bidi/zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="], - "cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -1889,29 +2380,37 @@ "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - "execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + "eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "eventid/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], - "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "execa/@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "execa/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], "express/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "gaxios/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + "glob/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], "globby/slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], - "handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "google-gax/@grpc/proto-loader": ["@grpc/proto-loader@0.7.15", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ=="], - "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "got/keyv": ["keyv@5.5.4", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ=="], - "is-bun-module/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "istanbul-lib-instrument/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "istanbul-lib-source-maps/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "jest-cli/yargs": ["yargs@17.7.2", "", { "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" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + "jest-changed-files/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], "jest-config/glob": ["glob@7.2.3", "", { "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" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], @@ -1925,43 +2424,53 @@ "jest-runtime/strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], - "jest-snapshot/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - "make-dir/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + "path-scurry/lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="], "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + "read-pkg/parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], + + "read-pkg/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + "schema-utils/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + "schema-utils/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + "send/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], - "string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "string-length/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], "sucrase/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], - "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "teeny-request/http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="], - "test-exclude/glob": ["glob@7.2.3", "", { "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" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "teeny-request/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], - "ts-jest/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], - "ts-loader/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "test-exclude/glob": ["glob@7.2.3", "", { "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" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "ts-loader/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], @@ -1975,23 +2484,33 @@ "webpack-cli/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], - "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@browseros/agent/@types/bun/bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], "@browseros/codex-sdk-ts/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "@browseros/tools/@types/bun/bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], + + "@google/gemini-cli-core/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "@google/gemini-cli-core/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "@google/gemini-cli-core/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@google/gemini-cli-core/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -1999,11 +2518,11 @@ "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - "@jest/reporters/jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + "@jest/core/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@puppeteer/browsers/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + "@jest/reporters/jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - "@puppeteer/browsers/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@jest/reporters/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], @@ -2013,19 +2532,23 @@ "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "chrome-devtools-mcp/puppeteer-core/@puppeteer/browsers": ["@puppeteer/browsers@2.10.12", "", { "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.3", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-mP9iLFZwH+FapKJLeA7/fLqOlSUwYpMwjR1P5J23qd4e7qGJwecJccJqHYrjw33jmIZYV4dtiTHPD/J+1e7cEw=="], + "babel-plugin-istanbul/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "chrome-devtools-mcp/puppeteer-core/chromium-bidi": ["chromium-bidi@10.5.1", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-rlj6OyhKhVTnk4aENcUme3Jl9h+cq4oXu4AzBcvr8RMmT6BR4a3zSNT9dbIfXr9/BS6ibzRyDhowuw4n2GgzsQ=="], + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "chrome-devtools-mcp/puppeteer-core/webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.3.8", "", {}, "sha512-21Yi2GhGntMc671vNBCjiAeEVknXjVRoyu+k+9xOMShu+ZQfpGQwnBqbNz/Sv4GXZ6JmutlPAi2nIJcrymAWuQ=="], + "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "jest-changed-files/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], - "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "jest-changed-files/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "jest-changed-files/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "jest-changed-files/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], - "jest-cli/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + "jest-changed-files/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "jest-cli/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "jest-changed-files/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], "jest-haste-map/jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], @@ -2033,13 +2556,21 @@ "jest-runner/source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "string-length/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "sucrase/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], @@ -2047,27 +2578,25 @@ "sucrase/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "teeny-request/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - "webpack/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], - - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "teeny-request/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + "webpack/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], - "@puppeteer/browsers/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@puppeteer/browsers/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "chrome-devtools-mcp/puppeteer-core/@puppeteer/browsers/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "chrome-devtools-mcp/puppeteer-core/@puppeteer/browsers/yargs": ["yargs@17.7.2", "", { "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" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + "@google/gemini-cli-core/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "jest-cli/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "@google/gemini-cli-core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "jest-cli/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], @@ -2077,20 +2606,6 @@ "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - "@puppeteer/browsers/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "chrome-devtools-mcp/puppeteer-core/@puppeteer/browsers/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - - "chrome-devtools-mcp/puppeteer-core/@puppeteer/browsers/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "jest-cli/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - - "chrome-devtools-mcp/puppeteer-core/@puppeteer/browsers/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "chrome-devtools-mcp/puppeteer-core/@puppeteer/browsers/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "chrome-devtools-mcp/puppeteer-core/@puppeteer/browsers/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], } } diff --git a/package.json b/package.json index fdd99cb..a396a6d 100644 --- a/package.json +++ b/package.json @@ -50,9 +50,11 @@ "commander": "^14.0.1", "core-js": "3.45.1", "debug": "4.4.3", + "hono": "^4.10.6", "mitt": "^3.0.1", "proxy-agent": "^6.5.0", "puppeteer-core": "24.23.0", + "semver": "^7.7.3", "smol-toml": "^1.4.2" }, "devDependencies": { diff --git a/packages/agent/package.json b/packages/agent/package.json index abb7eb3..586553d 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -28,10 +28,19 @@ "author": "", "license": "MIT", "dependencies": { - "@browseros/tools": "workspace:*", + "@ai-sdk/amazon-bedrock": "^3.0.59", + "@ai-sdk/anthropic": "^2.0.47", + "@ai-sdk/azure": "^2.0.74", + "@ai-sdk/google": "^2.0.43", + "@ai-sdk/openai": "^2.0.72", + "@ai-sdk/openai-compatible": "^1.0.27", + "@anthropic-ai/claude-agent-sdk": "^0.1.11", "@browseros/common": "workspace:*", "@browseros/server": "workspace:*", - "@anthropic-ai/claude-agent-sdk": "^0.1.11", + "@browseros/tools": "workspace:*", + "@google/gemini-cli-core": "^0.16.0", + "@openrouter/ai-sdk-provider": "~1.2.5", + "ai": "^5.0.101", "zod": "^4.1.12" }, "devDependencies": { diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/errors.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/errors.ts new file mode 100644 index 0000000..2ef1fb6 --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/errors.ts @@ -0,0 +1,68 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Conversion error with structured details + */ + +/** + * Structured error compatible with Gemini CLI error handling + */ +export interface StructuredError { + message: string; + status?: number; +} + +export interface ConversionErrorDetails { + /** Stage where conversion failed */ + stage: 'tool' | 'message' | 'response' | 'stream'; + + /** Specific operation that failed */ + operation: string; + + /** Input that caused the failure (sanitized, no secrets) */ + input?: unknown; + + /** Underlying error if available */ + cause?: Error; + + /** Additional context for debugging */ + context?: Record; +} + +export class ConversionError extends Error { + constructor( + message: string, + readonly details: ConversionErrorDetails, + ) { + super(message); + this.name = 'ConversionError'; + + // Maintain proper stack trace + if (Error.captureStackTrace) { + Error.captureStackTrace(this, ConversionError); + } + } + + /** + * Convert to StructuredError for Gemini CLI error handling + */ + toStructuredError(): StructuredError { + return { + message: `[${this.details.stage}] ${this.details.operation}: ${this.message}`, + status: 500, + }; + } + + /** + * Get user-friendly error message + */ + toFriendlyMessage(): string { + const stage = + this.details.stage.charAt(0).toUpperCase() + this.details.stage.slice(1); + return `${stage} conversion failed: ${this.message}`; + } +} diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts new file mode 100644 index 0000000..5878521 --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts @@ -0,0 +1,297 @@ +/** + * @license + * Copyright 2025 BrowserOS + */ + +/** + * Vercel AI ContentGenerator Implementation + * Multi-provider LLM adapter using Vercel AI SDK + */ + +import { streamText, generateText, convertToModelMessages } from 'ai'; +import { createAnthropic } from '@ai-sdk/anthropic'; +import { createOpenAI } from '@ai-sdk/openai'; +import { createGoogleGenerativeAI } from '@ai-sdk/google'; +import { createOpenRouter } from '@openrouter/ai-sdk-provider'; +import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; +import { createAzure } from '@ai-sdk/azure'; +import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'; + +import type { ContentGenerator } from '@google/gemini-cli-core'; +import type { HonoSSEStream } from './types.js'; +import type { + GenerateContentParameters, + GenerateContentResponse, + CountTokensParameters, + CountTokensResponse, + EmbedContentParameters, + EmbedContentResponse, + Content, +} from '@google/genai'; +import { + ToolConversionStrategy, + MessageConversionStrategy, + ResponseConversionStrategy, +} from './strategies/index.js'; +import type { VercelAIConfig } from './types.js'; + +/** + * Vercel AI ContentGenerator + * Implements ContentGenerator interface using strategy pattern for conversions + */ +export class VercelAIContentGenerator implements ContentGenerator { + private providerRegistry: Map unknown>; + private model: string; + private honoStream?: HonoSSEStream; + + // Conversion strategies + private toolStrategy: ToolConversionStrategy; + private messageStrategy: MessageConversionStrategy; + private responseStrategy: ResponseConversionStrategy; + + constructor(config: VercelAIConfig) { + this.model = config.model; + this.honoStream = config.honoStream; + this.providerRegistry = new Map(); + + // Initialize conversion strategies + this.toolStrategy = new ToolConversionStrategy(); + this.messageStrategy = new MessageConversionStrategy(); + this.responseStrategy = new ResponseConversionStrategy(this.toolStrategy); + + // Register providers based on config + this.registerProviders(config); + } + + /** + * Non-streaming content generation + */ + async generateContent( + request: GenerateContentParameters, + _userPromptId: string, + ): Promise { + const contents = (Array.isArray(request.contents) ? request.contents : [request.contents]) as Content[]; + const messages = this.messageStrategy.geminiToVercel(contents); + const tools = this.toolStrategy.geminiToVercel(request.config?.tools); + + const system = this.messageStrategy.convertSystemInstruction( + request.config?.systemInstruction, + ); + + const { provider, modelName } = this.parseModel( + request.model || this.model, + ); + const providerInstance = this.getProvider(provider); + + const result = await generateText({ + model: providerInstance(modelName) as Parameters< + typeof generateText + >[0]['model'], + messages, + system, + tools, + temperature: request.config?.temperature, + topP: request.config?.topP, + }); + + return this.responseStrategy.vercelToGemini(result); + } + + /** + * Streaming content generation + */ + async generateContentStream( + request: GenerateContentParameters, + _userPromptId: string, + ): Promise> { + const contents = (Array.isArray(request.contents) ? request.contents : [request.contents]) as Content[]; + const messages = this.messageStrategy.geminiToVercel(contents); + const tools = this.toolStrategy.geminiToVercel(request.config?.tools); + const system = this.messageStrategy.convertSystemInstruction( + request.config?.systemInstruction, + ); + + const { provider, modelName } = this.parseModel( + request.model || this.model, + ); + const providerInstance = this.getProvider(provider); + + const result = streamText({ + model: providerInstance(modelName) as Parameters< + typeof streamText + >[0]['model'], + messages, + system, + tools, + temperature: request.config?.temperature, + topP: request.config?.topP, + }); + + return this.responseStrategy.streamToGemini( + result.fullStream, + async () => { + try { + const usage = await result.usage; + return { + promptTokens: (usage as { promptTokens?: number }).promptTokens, + completionTokens: (usage as { completionTokens?: number }) + .completionTokens, + totalTokens: (usage as { totalTokens?: number }).totalTokens, + }; + } catch { + return undefined; + } + }, + this.honoStream, + ); + } + + /** + * Count tokens (estimation) + */ + async countTokens( + request: CountTokensParameters, + ): Promise { + // Rough estimation: 1 token ≈ 4 characters + const text = JSON.stringify(request.contents); + const estimatedTokens = Math.ceil(text.length / 4); + + return { + totalTokens: estimatedTokens, + }; + } + + /** + * Embed content (not universally supported) + */ + async embedContent( + _request: EmbedContentParameters, + ): Promise { + throw new Error( + 'Embeddings not universally supported across providers. ' + + 'Use provider-specific embedding endpoints.', + ); + } + + /** + * Register providers based on config + */ + private registerProviders(config: VercelAIConfig): void { + if (config.apiKeys?.anthropic) { + this.providerRegistry.set( + 'anthropic', + createAnthropic({ apiKey: config.apiKeys.anthropic }), + ); + } + + if (config.apiKeys?.openai) { + this.providerRegistry.set( + 'openai', + createOpenAI({ + apiKey: config.apiKeys.openai, + compatibility: 'strict', // Enable streaming token usage + }), + ); + } + + if (config.apiKeys?.google) { + this.providerRegistry.set( + 'google', + createGoogleGenerativeAI({ apiKey: config.apiKeys.google }), + ); + } + + if (config.apiKeys?.openrouter) { + this.providerRegistry.set( + 'openrouter', + createOpenRouter({ apiKey: config.apiKeys.openrouter }), + ); + } + + if (config.apiKeys?.azure && config.azureResourceName) { + this.providerRegistry.set( + 'azure', + createAzure({ + resourceName: config.azureResourceName, + apiKey: config.apiKeys.azure, + }), + ); + } + + if (config.lmstudioBaseUrl !== undefined) { + this.providerRegistry.set( + 'lmstudio', + createOpenAICompatible({ + name: 'lmstudio', + baseURL: config.lmstudioBaseUrl || 'http://localhost:1234/v1', + }), + ); + } + + if (config.ollamaBaseUrl !== undefined) { + this.providerRegistry.set( + 'ollama', + createOpenAICompatible({ + name: 'ollama', + baseURL: config.ollamaBaseUrl || 'http://localhost:11434/v1', + }), + ); + } + + if ( + config.awsAccessKeyId && + config.awsSecretAccessKey && + config.awsRegion + ) { + this.providerRegistry.set( + 'bedrock', + createAmazonBedrock({ + region: config.awsRegion, + accessKeyId: config.awsAccessKeyId, + secretAccessKey: config.awsSecretAccessKey, + sessionToken: config.awsSessionToken, + }), + ); + } + } + + /** + * Parse model string into provider and model name + */ + private parseModel(modelString: string): { + provider: string; + modelName: string; + } { + const parts = modelString.split('/'); + + if (parts.length < 2) { + throw new Error( + `Invalid model format: "${modelString}". ` + + `Expected "provider/model-name" (e.g., "anthropic/claude-3-5-sonnet-20241022")`, + ); + } + + const provider = parts[0]; + const modelName = parts.slice(1).join('/'); + + return { provider, modelName }; + } + + /** + * Get provider instance or throw error + */ + private getProvider(provider: string): (modelId: string) => unknown { + const providerInstance = this.providerRegistry.get(provider); + + if (!providerInstance) { + const available = Array.from(this.providerRegistry.keys()).join(', '); + throw new Error( + `Provider "${provider}" not configured. ` + + `Available providers: ${available || 'none'}. ` + + `Add API key in config.apiKeys.${provider}`, + ); + } + + return providerInstance; + } +} diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/index.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/index.ts new file mode 100644 index 0000000..730ddf3 --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/index.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Strategies barrel export + * Single entry point for all conversion strategies + */ + +export { ToolConversionStrategy } from './tool.js'; +export { MessageConversionStrategy } from './message.js'; +export { ResponseConversionStrategy } from './response.js'; diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.test.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.test.ts new file mode 100644 index 0000000..5bf008e --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.test.ts @@ -0,0 +1,706 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Unit tests for MessageConversionStrategy + * + * REQUIREMENTS-BASED TESTS + * These tests verify the adapter meets the type contracts between: + * - Gemini SDK: Content, Part, FunctionCall, FunctionResponse + * - Vercel AI SDK: CoreMessage (UserModelMessage, AssistantModelMessage, ToolModelMessage) + * + * Key Type Contracts: + * - Content.role: 'user' | 'model' (maps to 'user' | 'assistant' | 'tool') + * - Content.parts is OPTIONAL (defaults to []) + * - CoreMessage.content can be: string | Array + * - ToolModelMessage.role MUST be 'tool' (not 'user') for function responses + * - Tool call parts use 'input' property per AI SDK v5 ToolCallPart interface + * - Tool result parts use 'output' property with structured format per AI SDK v5 + * - Empty messages (no text, no parts) should be skipped + */ + +import { describe, it as t, expect, beforeEach } from 'vitest'; +import { MessageConversionStrategy } from './message.js'; +import type { + Content, + FunctionResponse, + FunctionCall, + ContentUnion, +} from '@google/genai'; +import type { + VercelContentPart, + VercelToolResultPart, + VercelToolCallPart, +} from '../types.js'; + +describe('MessageConversionStrategy', () => { + let strategy: MessageConversionStrategy; + + beforeEach(() => { + strategy = new MessageConversionStrategy(); + }); + + // ======================================== + // GEMINI → VERCEL (Conversation History) + // ======================================== + + describe('geminiToVercel', () => { + // Empty and edge cases + + t('tests that empty contents array returns empty array', () => { + const result = strategy.geminiToVercel([]); + expect(result).toEqual([]); + }); + + t('tests that content with undefined parts is skipped', () => { + const contents: Content[] = [{ role: 'user', parts: undefined }]; + + const result = strategy.geminiToVercel(contents); + + expect(result).toHaveLength(0); + }); + + t('tests that content with empty parts array is skipped', () => { + const contents: Content[] = [{ role: 'user', parts: [] }]; + + const result = strategy.geminiToVercel(contents); + + expect(result).toHaveLength(0); + }); + + t( + 'tests that content with no text and no function parts is skipped', + () => { + const contents: Content[] = [{ role: 'user', parts: [{ text: '' }] }]; + + const result = strategy.geminiToVercel(contents); + + expect(result).toHaveLength(0); + }, + ); + + // Simple text messages + + t('tests that simple user text message converts to string content', () => { + const contents: Content[] = [ + { + role: 'user', + parts: [{ text: 'Hello world' }], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + expect(result).toHaveLength(1); + expect(result[0].role).toBe('user'); + expect(result[0].content).toBe('Hello world'); + }); + + t('tests that model role maps to assistant role', () => { + const contents: Content[] = [ + { + role: 'model', + parts: [{ text: 'Hi there!' }], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + expect(result[0].role).toBe('assistant'); + expect(result[0].content).toBe('Hi there!'); + }); + + t('tests that multiple text parts join with newline', () => { + const contents: Content[] = [ + { + role: 'user', + parts: [{ text: 'Line 1' }, { text: 'Line 2' }, { text: 'Line 3' }], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + expect(result[0].content).toBe('Line 1\nLine 2\nLine 3'); + }); + + // Tool result messages (function responses from user) + + t( + 'tests that function response converts to tool role not user role', + () => { + const contents: Content[] = [ + { + role: 'user', + parts: [ + { + functionResponse: { + id: 'call_123', + name: 'get_weather', + response: { temperature: 72, condition: 'sunny' }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + // CRITICAL: Must be 'tool' role, not 'user' + expect(result[0].role).toBe('tool'); + }, + ); + + t( + 'tests that function response content is array of tool-result parts', + () => { + const contents: Content[] = [ + { + role: 'user', + parts: [ + { + functionResponse: { + id: 'call_456', + name: 'search', + response: { results: ['result1', 'result2'] }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + expect(Array.isArray(result[0].content)).toBe(true); + const content = result[0].content as VercelContentPart[]; + const toolResult = content[0] as VercelToolResultPart; + expect(toolResult.type).toBe('tool-result'); + expect(toolResult.toolCallId).toBe('call_456'); + expect(toolResult.toolName).toBe('search'); + }, + ); + + t('tests that function response output contains structured response per v5', () => { + const contents: Content[] = [ + { + role: 'user', + parts: [ + { + functionResponse: { + id: 'call_789', + name: 'get_data', + response: { data: 'test', success: true }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + const toolResult = content[0] as VercelToolResultPart; + // AI SDK v5 uses structured output format + expect(toolResult.output).toEqual({ type: 'json', value: { data: 'test', success: true } }); + }); + + t( + 'tests that function response with error field uses error output type', + () => { + const contents: Content[] = [ + { + role: 'user', + parts: [ + { + functionResponse: { + id: 'call_error', + name: 'broken_tool', + response: { error: 'Something went wrong', code: 500 }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + const toolResult = content[0] as VercelToolResultPart; + // AI SDK v5 uses error-text or error-json for error responses + expect(toolResult.output).toEqual({ + type: 'error-text', + value: 'Something went wrong', + }); + }, + ); + + t( + 'tests that function response without response field uses empty json output', + () => { + const contents: Content[] = [ + { + role: 'user', + parts: [ + { + functionResponse: { + id: 'call_no_response', + name: 'simple_tool', + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + const toolResult = content[0] as VercelToolResultPart; + // AI SDK v5 uses structured output format + expect(toolResult.output).toEqual({ type: 'json', value: {} }); + }, + ); + + t('tests that function response without id generates one', () => { + const contents: Content[] = [ + { + role: 'user', + parts: [ + { + functionResponse: { + name: 'test_tool', + response: { result: 'ok' }, + } as Partial as FunctionResponse, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + const toolResult = content[0] as VercelToolResultPart; + expect(toolResult.toolCallId).toBeDefined(); + expect(toolResult.toolCallId).toMatch(/^call_\d+_[a-z0-9]+$/); + }); + + t('tests that function response without name uses unknown', () => { + const contents: Content[] = [ + { + role: 'user', + parts: [ + { + functionResponse: { + id: 'call_no_name', + response: { result: 'ok' }, + } as Partial as FunctionResponse, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + const toolResult = content[0] as VercelToolResultPart; + expect(toolResult.toolName).toBe('unknown'); + }); + + t( + 'tests that multiple function responses in one message all convert', + () => { + const contents: Content[] = [ + { + role: 'user', + parts: [ + { + functionResponse: { + id: 'call_1', + name: 'tool1', + response: { result: 1 }, + }, + }, + { + functionResponse: { + id: 'call_2', + name: 'tool2', + response: { result: 2 }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + expect(content).toHaveLength(2); + const toolResult0 = content[0] as VercelToolResultPart; + const toolResult1 = content[1] as VercelToolResultPart; + expect(toolResult0.toolCallId).toBe('call_1'); + expect(toolResult1.toolCallId).toBe('call_2'); + }, + ); + + // Assistant messages with tool calls + + t( + 'tests that function call converts to assistant message with tool-call part', + () => { + const contents: Content[] = [ + { + role: 'model', + parts: [ + { + functionCall: { + id: 'call_abc', + name: 'search', + args: { query: 'test' }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + expect(result[0].role).toBe('assistant'); + const content = result[0].content as VercelContentPart[]; + expect(content).toHaveLength(1); + const toolCall = content[0] as VercelToolCallPart; + expect(toolCall.type).toBe('tool-call'); + expect(toolCall.toolCallId).toBe('call_abc'); + expect(toolCall.toolName).toBe('search'); + }, + ); + + t( + 'tests that function call uses input property per SDK v5 ToolCallPart interface', + () => { + const contents: Content[] = [ + { + role: 'model', + parts: [ + { + functionCall: { + id: 'call_def', + name: 'get_weather', + args: { location: 'Tokyo', units: 'celsius' }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + const toolCall = content[0] as VercelToolCallPart; + // CRITICAL: Must be 'input' per Vercel AI SDK v5's ToolCallPart interface + expect(toolCall).toHaveProperty('input'); + expect(toolCall.input).toEqual({ + location: 'Tokyo', + units: 'celsius', + }); + }, + ); + + t( + 'tests that assistant message with text and tool call includes both', + () => { + const contents: Content[] = [ + { + role: 'model', + parts: [ + { text: 'Let me search for that' }, + { + functionCall: { + id: 'call_search', + name: 'search', + args: { query: 'test' }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + expect(content).toHaveLength(2); + expect(content[0].type).toBe('text'); + if ('text' in content[0]) { + expect(content[0].text).toBe('Let me search for that'); + } + expect(content[1].type).toBe('tool-call'); + }, + ); + + t('tests that function call without id generates one', () => { + const contents: Content[] = [ + { + role: 'model', + parts: [ + { + functionCall: { + name: 'test_tool', + args: { test: true }, + } as Partial as FunctionCall, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + const toolCall = content[0] as VercelToolCallPart; + expect(toolCall.toolCallId).toBeDefined(); + expect(toolCall.toolCallId).toMatch(/^call_\d+_[a-z0-9]+$/); + }); + + t('tests that function call without name uses unknown', () => { + const contents: Content[] = [ + { + role: 'model', + parts: [ + { + functionCall: { + id: 'call_xyz', + args: { test: true }, + } as Partial as FunctionCall, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + const toolCall = content[0] as VercelToolCallPart; + expect(toolCall.toolName).toBe('unknown'); + }); + + t('tests that function call without args uses empty object', () => { + const contents: Content[] = [ + { + role: 'model', + parts: [ + { + functionCall: { + id: 'call_no_args', + name: 'simple_tool', + } as Partial as FunctionCall, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + const toolCall = content[0] as VercelToolCallPart; + expect(toolCall.input).toEqual({}); + }); + + t('tests that multiple function calls in one message all convert', () => { + const contents: Content[] = [ + { + role: 'model', + parts: [ + { + functionCall: { + id: 'call_1', + name: 'tool1', + args: { arg: 'val1' }, + }, + }, + { + functionCall: { + id: 'call_2', + name: 'tool2', + args: { arg: 'val2' }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(contents); + + const content = result[0].content as VercelContentPart[]; + expect(content).toHaveLength(2); + const toolCall0 = content[0] as VercelToolCallPart; + const toolCall1 = content[1] as VercelToolCallPart; + expect(toolCall0.toolName).toBe('tool1'); + expect(toolCall1.toolName).toBe('tool2'); + }); + + // Multi-turn conversations + + t( + 'tests that multi-turn conversation with mixed message types converts correctly', + () => { + const contents: Content[] = [ + { role: 'user', parts: [{ text: 'Hello' }] }, + { role: 'model', parts: [{ text: 'Hi! How can I help?' }] }, + { role: 'user', parts: [{ text: 'Search for cats' }] }, + { + role: 'model', + parts: [ + { + functionCall: { + id: 'call_search', + name: 'search', + args: { query: 'cats' }, + }, + }, + ], + }, + { + role: 'user', + parts: [ + { + functionResponse: { + id: 'call_search', + name: 'search', + response: { results: ['cat1', 'cat2'] }, + }, + }, + ], + }, + { role: 'model', parts: [{ text: 'Found 2 results' }] }, + ]; + + const result = strategy.geminiToVercel(contents); + + expect(result).toHaveLength(6); + expect(result[0].role).toBe('user'); + expect(result[1].role).toBe('assistant'); + expect(result[2].role).toBe('user'); + expect(result[3].role).toBe('assistant'); + expect(result[4].role).toBe('tool'); // Not 'user'! + expect(result[5].role).toBe('assistant'); + }, + ); + }); + + // ======================================== + // SYSTEM INSTRUCTION CONVERSION + // ======================================== + + describe('convertSystemInstruction', () => { + t('tests that undefined instruction returns undefined', () => { + const result = strategy.convertSystemInstruction(undefined); + expect(result).toBeUndefined(); + }); + + t('tests that string instruction returns same string', () => { + const result = strategy.convertSystemInstruction( + 'You are a helpful assistant', + ); + expect(result).toBe('You are a helpful assistant'); + }); + + t( + 'tests that empty string instruction returns undefined per implementation', + () => { + const result = strategy.convertSystemInstruction(''); + // Empty strings are falsy, should return undefined + expect(result).toBeUndefined(); + }, + ); + + t( + 'tests that Content object with text parts extracts and joins text', + () => { + const instruction = { + parts: [{ text: 'System instruction here' }], + }; + + const result = strategy.convertSystemInstruction( + instruction as ContentUnion, + ); + + expect(result).toBe('System instruction here'); + }, + ); + + t( + 'tests that Content object with multiple text parts joins with newline', + () => { + const instruction = { + parts: [{ text: 'Line 1' }, { text: 'Line 2' }], + }; + + const result = strategy.convertSystemInstruction( + instruction as ContentUnion, + ); + + expect(result).toBe('Line 1\nLine 2'); + }, + ); + + t('tests that Content object with empty parts returns undefined', () => { + const instruction = { + parts: [], + }; + + const result = strategy.convertSystemInstruction( + instruction as ContentUnion, + ); + + expect(result).toBeUndefined(); + }); + + t('tests that Content object with non-text parts returns undefined', () => { + const instruction = { + parts: [ + { + functionCall: { + id: 'test', + name: 'test', + args: {}, + }, + }, + ], + }; + + const result = strategy.convertSystemInstruction( + instruction as ContentUnion, + ); + + expect(result).toBeUndefined(); + }); + + t( + 'tests that Content object with undefined parts returns undefined', + () => { + const instruction = { + parts: undefined, + }; + + const result = strategy.convertSystemInstruction( + instruction as ContentUnion, + ); + + expect(result).toBeUndefined(); + }, + ); + + t('tests that invalid input type returns undefined', () => { + const result = strategy.convertSystemInstruction( + 123 as unknown as ContentUnion, + ); + expect(result).toBeUndefined(); + }); + + t('tests that null input returns undefined', () => { + const result = strategy.convertSystemInstruction( + null as unknown as ContentUnion, + ); + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.ts new file mode 100644 index 0000000..f71688a --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.ts @@ -0,0 +1,283 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Message Conversion Strategy + * Converts conversation history from Gemini to Vercel format + */ + +import type { + CoreMessage, + VercelContentPart, + LanguageModelV2ToolResultOutput, +} from '../types.js'; +import type { Content, ContentUnion } from '@google/genai'; +import { + isTextPart, + isFunctionCallPart, + isFunctionResponsePart, + isInlineDataPart, +} from '../utils/type-guards.js'; + +export class MessageConversionStrategy { + /** + * Convert Gemini conversation history to Vercel messages + * + * @param contents - Array of Gemini Content objects + * @returns Array of Vercel CoreMessage objects + */ + geminiToVercel(contents: readonly Content[]): CoreMessage[] { + const messages: CoreMessage[] = []; + const seenToolResultIds = new Set(); + + for (const content of contents) { + const role = content.role === 'model' ? 'assistant' : 'user'; + + // Separate parts by type + const textParts: string[] = []; + const functionCalls: Array<{ + id?: string; + name?: string; + args?: Record; + }> = []; + const functionResponses: Array<{ + id?: string; + name?: string; + response?: Record; + }> = []; + const imageParts: Array<{ + mimeType: string; + data: string; + }> = []; + + for (const part of content.parts || []) { + if (isTextPart(part)) { + textParts.push(part.text); + } else if (isFunctionCallPart(part)) { + functionCalls.push(part.functionCall); + } else if (isFunctionResponsePart(part)) { + functionResponses.push(part.functionResponse); + } else if (isInlineDataPart(part)) { + imageParts.push(part.inlineData); + } + } + + const textContent = textParts.join('\n'); + + // CASE 1: Simple text message (possibly with images) + if (functionCalls.length === 0 && functionResponses.length === 0) { + if (imageParts.length > 0) { + // Multi-part message with text and images + + const contentParts: VercelContentPart[] = []; + + if (textContent) { + contentParts.push({ + type: 'text', + text: textContent, + }); + } + + for (const img of imageParts) { + contentParts.push({ + type: 'image', + image: img.data, // Pass raw base64 string + mediaType: img.mimeType, + }); + } + + messages.push({ + role: role as 'user' | 'assistant', + content: contentParts, + } as CoreMessage); + } else if (textContent) { + messages.push({ + role: role as 'user' | 'assistant', + content: textContent, + }); + } + continue; + } + + // CASE 2: Tool results (user providing tool execution results) + if (functionResponses.length > 0) { + + // Filter out duplicate tool results based on ID + const uniqueResponses = functionResponses.filter((fr) => { + const id = fr.id || ''; + if (seenToolResultIds.has(id)) { + return false; + } + seenToolResultIds.add(id); + return true; + }); + + // If all tool results were duplicates, skip this message entirely + if (uniqueResponses.length === 0) { + continue; + } + + // If there are NO images → standard tool message + if (imageParts.length === 0) { + const toolResultParts = this.convertFunctionResponsesToToolResults(uniqueResponses); + messages.push({ + role: 'tool', + content: toolResultParts, + } as unknown as CoreMessage); + continue; + } + + // If there ARE images → create TWO messages: + // 1. Tool message (satisfies OpenAI requirement that tool_calls must be followed by tool messages) + // 2. User message with images (tool messages don't support images) + + // Message 1: Tool message with tool results (no images) + const toolResultParts = this.convertFunctionResponsesToToolResults(uniqueResponses); + messages.push({ + role: 'tool', + content: toolResultParts, + } as unknown as CoreMessage); + + // Message 2: User message with images + const userContentParts: VercelContentPart[] = []; + + // Add explanatory text + userContentParts.push({ + type: 'text', + text: `Here are the screenshots from the tool execution:`, + }); + + // Add images as raw base64 string (will be converted to data URL by OpenAI provider) + for (const img of imageParts) { + userContentParts.push({ + type: 'image', + image: img.data, + mediaType: img.mimeType, + }); + } + + messages.push({ + role: 'user', + content: userContentParts, + } as CoreMessage); + continue; + } + + // CASE 3: Assistant with tool calls + if (role === 'assistant' && functionCalls.length > 0) { + const contentParts: VercelContentPart[] = []; + + // Add text if present + if (textContent) { + contentParts.push({ + type: 'text' as const, + text: textContent, + }); + } + + // Add tool calls + // CRITICAL: Use 'input' property - this is what ToolCallPart expects per AI SDK v5 + for (const fc of functionCalls) { + contentParts.push({ + type: 'tool-call' as const, + toolCallId: fc.id || this.generateToolCallId(), + toolName: fc.name || 'unknown', + input: fc.args || {}, + }); + } + + messages.push({ + role: 'assistant', + content: contentParts, + } as CoreMessage); + continue; + } + } + + return messages; + } + + /** + * Convert system instruction to plain text + * + * @param instruction - Gemini system instruction (string, Content, or Part) + * @returns Plain text string or undefined + */ + convertSystemInstruction( + instruction: ContentUnion | undefined, + ): string | undefined { + if (!instruction) { + return undefined; + } + + // Handle string input + if (typeof instruction === 'string') { + return instruction; + } + + // Handle Content object with parts + if (typeof instruction === 'object' && 'parts' in instruction) { + const textParts = (instruction.parts || []) + .filter(isTextPart) + .map((p) => p.text); + + return textParts.length > 0 ? textParts.join('\n') : undefined; + } + + return undefined; + } + + /** + * Convert function responses to tool result parts for AI SDK v5 + */ + private convertFunctionResponsesToToolResults( + responses: Array<{ + id?: string; + name?: string; + response?: Record; + }>, + ): VercelContentPart[] { + return responses.map((fr) => { + // Convert Gemini response to AI SDK v5 structured output format + let output: LanguageModelV2ToolResultOutput; + const response = fr.response || {}; + + // Check for error first + if (typeof response === 'object' && 'error' in response && response.error) { + output = { + type: typeof response.error === 'string' ? 'error-text' : 'error-json', + value: response.error, + }; + } else if (typeof response === 'object' && 'output' in response) { + // Gemini's explicit output format: {output: value} + output = { + type: typeof response.output === 'string' ? 'text' : 'json', + value: response.output, + }; + } else { + // Whole response is the output + output = { + type: typeof response === 'string' ? 'text' : 'json', + value: response, + }; + } + + return { + type: 'tool-result' as const, + toolCallId: fr.id || this.generateToolCallId(), + toolName: fr.name || 'unknown', + output: output, + }; + }); + } + + /** + * Generate unique tool call ID + */ + private generateToolCallId(): string { + return `call_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`; + } +} diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.test.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.test.ts new file mode 100644 index 0000000..c8ee08d --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.test.ts @@ -0,0 +1,550 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Unit tests for ResponseConversionStrategy + * + * REQUIREMENTS-BASED TESTS + * These tests verify the adapter meets the type contracts between: + * - Vercel AI SDK: generateText result, stream chunks + * - Gemini SDK: GenerateContentResponse + * + * Key Type Contracts: + * - Response MUST include functionCalls at TOP LEVEL (not just in parts) + * - FinishReason mapping: stop/tool-calls→STOP, length/max-tokens→MAX_TOKENS, etc. + * - Usage metadata fields are OPTIONAL (can be undefined) + * - Stream chunks: text-delta (yield immediately), tool-call (accumulate), finish + * - Usage retrieval is ASYNC and happens AFTER stream (may fail) + */ + +import { describe, it as t, expect, beforeEach } from 'vitest'; +import { ResponseConversionStrategy } from './response.js'; +import { ToolConversionStrategy } from './tool.js'; +import type { GenerateContentResponse } from '@google/genai'; +import { FinishReason } from '@google/genai'; + +describe('ResponseConversionStrategy', () => { + let strategy: ResponseConversionStrategy; + let toolStrategy: ToolConversionStrategy; + + beforeEach(() => { + toolStrategy = new ToolConversionStrategy(); + strategy = new ResponseConversionStrategy(toolStrategy); + }); + + // ======================================== + // NON-STREAMING CONVERSION + // ======================================== + + describe('vercelToGemini (non-streaming)', () => { + t('tests that simple text result converts to Gemini response', () => { + const vercelResult = { + text: 'Hello world', + finishReason: 'stop' as const, + usage: { + inputTokens: 10, + outputTokens: 5, + totalTokens: 15, + }, + }; + + const result = strategy.vercelToGemini(vercelResult); + + expect(result.candidates).toBeDefined(); + expect(result.candidates).toHaveLength(1); + expect(result.candidates![0].content!.role).toBe('model'); + expect(result.candidates![0].content!.parts).toHaveLength(1); + expect(result.candidates![0].content!.parts![0]).toEqual({ + text: 'Hello world', + }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.STOP); + expect(result.candidates![0].index).toBe(0); + }); + + t('tests that usage metadata maps correctly', () => { + const vercelResult = { + text: 'Test', + usage: { + inputTokens: 100, + outputTokens: 50, + totalTokens: 150, + }, + }; + + const result = strategy.vercelToGemini(vercelResult); + + expect(result.usageMetadata).toBeDefined(); + expect(result.usageMetadata?.promptTokenCount).toBe(100); + expect(result.usageMetadata?.candidatesTokenCount).toBe(50); + expect(result.usageMetadata?.totalTokenCount).toBe(150); + }); + + t( + 'tests that result with tool calls includes functionCalls at top level', + () => { + const vercelResult = { + text: '', + toolCalls: [ + { + toolCallId: 'call_123', + toolName: 'get_weather', + args: { location: 'Tokyo' }, + }, + ], + finishReason: 'tool-calls' as const, + }; + + const result = strategy.vercelToGemini(vercelResult); + + // CRITICAL: Must have functionCalls at TOP LEVEL for turn.ts + expect(result.functionCalls).toBeDefined(); + expect(result.functionCalls).toHaveLength(1); + expect(result.functionCalls![0].id).toBe('call_123'); + expect(result.functionCalls![0].name).toBe('get_weather'); + expect(result.functionCalls![0].args).toEqual({ location: 'Tokyo' }); + }, + ); + + t( + 'tests that tool calls appear in both parts and top-level functionCalls', + () => { + const vercelResult = { + text: '', + toolCalls: [ + { + toolCallId: 'call_456', + toolName: 'search', + args: { query: 'test' }, + }, + ], + }; + + const result = strategy.vercelToGemini(vercelResult); + + // Should be in parts + expect(result.candidates![0].content!.parts).toHaveLength(1); + expect(result.candidates![0].content!.parts![0]).toHaveProperty( + 'functionCall', + ); + + // Should ALSO be at top level + expect(result.functionCalls).toHaveLength(1); + expect(result.functionCalls![0].name).toBe('search'); + }, + ); + + t('tests that text and tool calls both appear in parts', () => { + const vercelResult = { + text: 'Let me check the weather', + toolCalls: [ + { + toolCallId: 'call_789', + toolName: 'get_weather', + args: { location: 'Paris' }, + }, + ], + }; + + const result = strategy.vercelToGemini(vercelResult); + + expect(result.candidates![0].content!.parts).toHaveLength(2); + expect(result.candidates![0].content!.parts![0]).toEqual({ + text: 'Let me check the weather', + }); + expect(result.candidates![0].content!.parts![1]).toHaveProperty( + 'functionCall', + ); + }); + + t('tests that multiple tool calls all convert', () => { + const vercelResult = { + text: '', + toolCalls: [ + { toolCallId: 'call_1', toolName: 'tool1', args: { arg: 'val1' } }, + { toolCallId: 'call_2', toolName: 'tool2', args: { arg: 'val2' } }, + ], + }; + + const result = strategy.vercelToGemini(vercelResult); + + expect(result.functionCalls).toHaveLength(2); + expect(result.candidates![0].content!.parts).toHaveLength(2); + }); + + t('tests that empty text is not included in parts', () => { + const vercelResult = { + text: '', + finishReason: 'stop' as const, + }; + + const result = strategy.vercelToGemini(vercelResult); + + // Empty text should be skipped + expect(result.candidates![0].content!.parts).toHaveLength(0); + }); + + t('tests that missing usage returns undefined usageMetadata', () => { + const vercelResult = { + text: 'Test', + finishReason: 'stop' as const, + }; + + const result = strategy.vercelToGemini(vercelResult); + + expect(result.usageMetadata).toBeUndefined(); + }); + + t('tests that usage with undefined fields defaults to 0', () => { + const vercelResult = { + text: 'Test', + usage: { + inputTokens: undefined, + outputTokens: 5, + totalTokens: undefined, + }, + }; + + const result = strategy.vercelToGemini(vercelResult); + + expect(result.usageMetadata?.promptTokenCount).toBe(0); + expect(result.usageMetadata?.candidatesTokenCount).toBe(5); + expect(result.usageMetadata?.totalTokenCount).toBe(0); + }); + + // Finish reason mapping tests + + t('tests that stop finish reason maps to STOP', () => { + const result = strategy.vercelToGemini({ + text: 'Test', + finishReason: 'stop' as const, + }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.STOP); + }); + + t('tests that tool-calls finish reason maps to STOP', () => { + const result = strategy.vercelToGemini({ + text: '', + toolCalls: [{ toolCallId: 'call_1', toolName: 'tool', args: {} }], + finishReason: 'tool-calls' as const, + }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.STOP); + }); + + t('tests that length finish reason maps to MAX_TOKENS', () => { + const result = strategy.vercelToGemini({ + text: 'Test', + finishReason: 'length' as const, + }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.MAX_TOKENS); + }); + + t('tests that max-tokens finish reason maps to MAX_TOKENS', () => { + const result = strategy.vercelToGemini({ + text: 'Test', + finishReason: 'max-tokens' as const, + }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.MAX_TOKENS); + }); + + t('tests that content-filter finish reason maps to SAFETY', () => { + const result = strategy.vercelToGemini({ + text: 'Test', + finishReason: 'content-filter' as const, + }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.SAFETY); + }); + + t('tests that error finish reason maps to OTHER', () => { + const result = strategy.vercelToGemini({ + text: 'Test', + finishReason: 'error' as const, + }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.OTHER); + }); + + t('tests that other finish reason maps to OTHER', () => { + const result = strategy.vercelToGemini({ + text: 'Test', + finishReason: 'other' as const, + }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.OTHER); + }); + + t('tests that unknown finish reason maps to OTHER', () => { + const result = strategy.vercelToGemini({ + text: 'Test', + finishReason: 'unknown' as const, + }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.OTHER); + }); + + t('tests that undefined finish reason defaults to STOP', () => { + const result = strategy.vercelToGemini({ text: 'Test' }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.STOP); + }); + + t( + 'tests that invalid result returns empty response without throwing', + () => { + const invalidResult = { + // Missing required 'text' field + finishReason: 'stop', + }; + + const result = strategy.vercelToGemini(invalidResult); + + expect(result.candidates).toHaveLength(1); + expect(result.candidates![0].content!.parts).toHaveLength(1); + expect(result.candidates![0].content!.parts![0]).toEqual({ text: '' }); + expect(result.candidates![0].finishReason!).toBe(FinishReason.OTHER); + }, + ); + }); + + // ======================================== + // STREAMING CONVERSION + // ======================================== + + describe('streamToGemini (streaming)', () => { + t( + 'tests that stream with text-delta chunks yields immediately', + async () => { + const stream = (async function* () { + yield { type: 'text-delta', textDelta: 'Hello' }; + yield { type: 'text-delta', textDelta: ' world' }; + yield { type: 'finish', finishReason: 'stop' as const }; + })(); + + const getUsage = async () => ({ totalTokens: 5 }); + + const chunks: GenerateContentResponse[] = []; + for await (const chunk of strategy.streamToGemini(stream, getUsage)) { + chunks.push(chunk); + } + + // Should yield text chunks immediately + expect(chunks.length).toBeGreaterThanOrEqual(2); + expect(chunks[0]!.candidates![0]!.content!.parts![0].text).toBe( + 'Hello', + ); + expect(chunks[1]!.candidates![0]!.content!.parts![0].text).toBe( + ' world', + ); + }, + ); + + t( + 'tests that stream with tool-call chunks accumulates and yields at end', + async () => { + const stream = (async function* () { + yield { + type: 'tool-call', + toolCallId: 'call_123', + toolName: 'get_weather', + args: { location: 'Tokyo' }, + }; + yield { type: 'finish', finishReason: 'tool-calls' as const }; + })(); + + const getUsage = async () => ({ totalTokens: 10 }); + + const chunks: GenerateContentResponse[] = []; + for await (const chunk of strategy.streamToGemini(stream, getUsage)) { + chunks.push(chunk); + } + + // Should yield final chunk with tool calls + const finalChunk = chunks[chunks.length - 1]; + expect(finalChunk!.functionCalls).toBeDefined(); + expect(finalChunk!.functionCalls).toHaveLength(1); + expect(finalChunk!.functionCalls![0].name).toBe('get_weather'); + }, + ); + + t( + 'tests that stream with multiple tool calls accumulates all', + async () => { + const stream = (async function* () { + yield { + type: 'tool-call', + toolCallId: 'call_1', + toolName: 'tool1', + args: { arg: 'val1' }, + }; + yield { + type: 'tool-call', + toolCallId: 'call_2', + toolName: 'tool2', + args: { arg: 'val2' }, + }; + yield { type: 'finish', finishReason: 'tool-calls' as const }; + })(); + + const getUsage = async () => ({ totalTokens: 15 }); + + const chunks: GenerateContentResponse[] = []; + for await (const chunk of strategy.streamToGemini(stream, getUsage)) { + chunks.push(chunk); + } + + const finalChunk = chunks[chunks.length - 1]; + expect(finalChunk!.functionCalls).toHaveLength(2); + }, + ); + + t('tests that stream with text and tool calls yields both', async () => { + const stream = (async function* () { + yield { type: 'text-delta', textDelta: 'Searching...' }; + yield { + type: 'tool-call', + toolCallId: 'call_search', + toolName: 'search', + args: { query: 'test' }, + }; + yield { type: 'finish', finishReason: 'tool-calls' as const }; + })(); + + const getUsage = async () => ({ totalTokens: 20 }); + + const chunks: GenerateContentResponse[] = []; + for await (const chunk of strategy.streamToGemini(stream, getUsage)) { + chunks.push(chunk); + } + + expect(chunks.length).toBeGreaterThanOrEqual(2); + // First chunk is text + expect(chunks[0]!.candidates![0]!.content!.parts![0]).toHaveProperty( + 'text', + ); + // Last chunk has tool calls + expect(chunks[chunks.length - 1].functionCalls).toHaveLength(1); + }); + + t( + 'tests that stream with unknown chunk types skips them gracefully', + async () => { + const stream = (async function* () { + yield { type: 'start' } as unknown; // Unknown type + yield { type: 'text-delta', textDelta: 'Hello' }; + yield { type: 'step-finish' } as unknown; // Unknown type + yield { type: 'finish', finishReason: 'stop' as const }; + })(); + + const getUsage = async () => ({ totalTokens: 5 }); + + const chunks: GenerateContentResponse[] = []; + for await (const chunk of strategy.streamToGemini(stream, getUsage)) { + chunks.push(chunk); + } + + // Should only process text-delta and finish + expect(chunks.length).toBeGreaterThanOrEqual(1); + }, + ); + + t('tests that stream with empty text-delta still yields', async () => { + const stream = (async function* () { + yield { type: 'text-delta', textDelta: '' }; + yield { type: 'finish', finishReason: 'stop' as const }; + })(); + + const getUsage = async () => ({ totalTokens: 0 }); + + const chunks: GenerateContentResponse[] = []; + for await (const chunk of strategy.streamToGemini(stream, getUsage)) { + chunks.push(chunk); + } + + expect(chunks.length).toBeGreaterThanOrEqual(1); + expect(chunks[0]!.candidates![0]!.content!.parts![0].text).toBe(''); + }); + + t('tests that stream without finish reason still completes', async () => { + const stream = (async function* () { + yield { type: 'text-delta', textDelta: 'Test' }; + // No finish chunk + })(); + + const getUsage = async () => ({ totalTokens: 5 }); + + const chunks: GenerateContentResponse[] = []; + for await (const chunk of strategy.streamToGemini(stream, getUsage)) { + chunks.push(chunk); + } + + expect(chunks.length).toBeGreaterThanOrEqual(1); + }); + + t( + 'tests that stream with getUsage error uses estimation fallback', + async () => { + const stream = (async function* () { + yield { type: 'text-delta', textDelta: 'Test message here' }; + yield { type: 'finish', finishReason: 'stop' as const }; + })(); + + const getUsage = async () => { + throw new Error('Usage not available'); + }; + + const chunks: GenerateContentResponse[] = []; + for await (const chunk of strategy.streamToGemini(stream, getUsage)) { + chunks.push(chunk); + } + + // Should still complete with estimated usage + const finalChunk = chunks[chunks.length - 1]; + expect(finalChunk!.usageMetadata?.totalTokenCount).toBeGreaterThan(0); + }, + ); + + t( + 'tests that stream with no content yields final metadata chunk', + async () => { + const stream = (async function* () { + // Empty stream + })(); + + const getUsage = async () => ({ totalTokens: 0 }); + + const chunks: GenerateContentResponse[] = []; + for await (const chunk of strategy.streamToGemini(stream, getUsage)) { + chunks.push(chunk); + } + + // Should yield final chunk with metadata + expect(chunks.length).toBe(1); + expect(chunks[0].usageMetadata).toBeDefined(); + }, + ); + + t( + 'tests that stream usage metadata is included in final chunk', + async () => { + const stream = (async function* () { + yield { type: 'text-delta', textDelta: 'Test' }; + yield { type: 'finish', finishReason: 'stop' as const }; + })(); + + const getUsage = async () => ({ + inputTokens: 10, + outputTokens: 5, + totalTokens: 15, + }); + + const chunks: GenerateContentResponse[] = []; + for await (const chunk of strategy.streamToGemini(stream, getUsage)) { + chunks.push(chunk); + } + + const finalChunk = chunks[chunks.length - 1]; + expect(finalChunk!.usageMetadata?.promptTokenCount).toBe(10); + expect(finalChunk!.usageMetadata?.candidatesTokenCount).toBe(5); + expect(finalChunk!.usageMetadata?.totalTokenCount).toBe(15); + }, + ); + }); +}); diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts new file mode 100644 index 0000000..28a691a --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts @@ -0,0 +1,341 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Response Conversion Strategy + * Converts LLM responses from Vercel to Gemini format + * Handles both streaming and non-streaming responses + */ + +import { GenerateContentResponse, FinishReason } from '@google/genai'; +import type { + Part, + FunctionCall, + VercelFinishReason, + VercelUsage, + HonoSSEStream, +} from '../types.js'; +import { + VercelGenerateTextResultSchema, + VercelStreamChunkSchema, +} from '../types.js'; +import type { ToolConversionStrategy } from './tool.js'; + +export class ResponseConversionStrategy { + constructor(private toolStrategy: ToolConversionStrategy) {} + + /** + * Convert Vercel generateText result to Gemini format + * + * @param result - Result from Vercel AI generateText() + * @returns Gemini GenerateContentResponse + */ + vercelToGemini(result: unknown): GenerateContentResponse { + // Validate with Zod + const parsed = VercelGenerateTextResultSchema.safeParse(result); + + if (!parsed.success) { + // Return minimal valid response + return this.createEmptyResponse(); + } + + const validated = parsed.data; + + const parts: Part[] = []; + let functionCalls: FunctionCall[] | undefined; + + // Add text content if present + if (validated.text) { + parts.push({ text: validated.text }); + } + + // Convert tool calls using ToolStrategy + if (validated.toolCalls && validated.toolCalls.length > 0) { + functionCalls = this.toolStrategy.vercelToGemini(validated.toolCalls); + + // Add to parts (dual representation for Gemini) + for (const fc of functionCalls) { + parts.push({ functionCall: fc }); + } + } + + // Handle usage metadata + const usageMetadata = this.convertUsage(validated.usage); + + // Create response with Object.setPrototypeOf pattern + // This allows setting readonly functionCalls property + return Object.setPrototypeOf( + { + candidates: [ + { + content: { + role: 'model', + parts, + }, + finishReason: this.mapFinishReason(validated.finishReason), + index: 0, + }, + ], + // CRITICAL: Top-level functionCalls for turn.ts compatibility + ...(functionCalls && functionCalls.length > 0 ? { functionCalls } : {}), + usageMetadata, + }, + GenerateContentResponse.prototype, + ); + } + + /** + * Convert Vercel stream to Gemini async generator + * DUAL OUTPUT: Emits raw Vercel chunks to Hono SSE + converts to Gemini format + * + * @param stream - AsyncIterable of Vercel stream chunks + * @param getUsage - Function to get usage metadata after stream completes + * @param honoStream - Optional Hono SSE stream for direct frontend streaming + * @returns AsyncGenerator yielding Gemini responses + */ + async *streamToGemini( + stream: AsyncIterable, + getUsage: () => Promise, + honoStream?: HonoSSEStream, + ): AsyncGenerator { + let textAccumulator = ''; + const toolCallsMap = new Map< + string, + { + toolCallId: string; + toolName: string; + args: unknown; + } + >(); + + let finishReason: VercelFinishReason | undefined; + + // Process stream chunks + for await (const rawChunk of stream) { + const chunkType = (rawChunk as { type?: string }).type; + + // Handle error chunks first + if (chunkType === 'error') { + const errorChunk = rawChunk as any; + const errorMessage = errorChunk.error?.message || errorChunk.error || 'Unknown error from LLM provider'; + throw new Error(`LLM Provider Error: ${errorMessage}`); + } + + // Try to parse as known chunk type + const parsed = VercelStreamChunkSchema.safeParse(rawChunk); + + if (!parsed.success) { + // Skip unknown chunk types (SDK emits many we don't process) + continue; + } + + const chunk = parsed.data; + + if (chunk.type === 'text-delta') { + const delta = chunk.text; + textAccumulator += delta; + + // Emit v5 SSE format to frontend: text-delta event + // v5 uses 'text' property, not 'textDelta' (v4) + if (honoStream) { + try { + const sseData = `data: ${JSON.stringify({ type: 'text-delta', text: delta })}\n\n`; + await honoStream.write(sseData); + } catch { + // Failed to write to stream + } + } + + yield Object.setPrototypeOf( + { + candidates: [ + { + content: { + role: 'model', + parts: [{ text: delta }], + }, + index: 0, + }, + ], + }, + GenerateContentResponse.prototype, + ); + } else if (chunk.type === 'tool-call') { + // Emit v5 SSE format to frontend: tool-call event + if (honoStream) { + try { + const sseData = `data: ${JSON.stringify({ + type: 'tool-call', + toolCallId: chunk.toolCallId, + toolName: chunk.toolName, + input: chunk.input, + })}\n\n`; + await honoStream.write(sseData); + } catch { + // Failed to write to stream + } + } + + toolCallsMap.set(chunk.toolCallId, { + toolCallId: chunk.toolCallId, + toolName: chunk.toolName, + input: chunk.input, + }); + } else if (chunk.type === 'finish') { + finishReason = chunk.finishReason; + } + } + + // Get usage metadata after stream completes + let usage: VercelUsage | undefined; + try { + usage = await getUsage(); + } catch { + // Fallback estimation + usage = this.estimateUsage(textAccumulator); + } + + // Emit final finish event in v5 SSE format + if (honoStream && (finishReason || usage)) { + try { + const finishData: any = { type: 'finish' }; + if (finishReason) { + finishData.finishReason = finishReason; + } + if (usage) { + finishData.usage = { + promptTokens: usage.promptTokens || 0, + completionTokens: usage.completionTokens || 0, + totalTokens: usage.totalTokens || 0, + }; + } + + const sseData = `data: ${JSON.stringify(finishData)}\n\n`; + await honoStream.write(sseData); + } catch { + // Failed to write to stream + } + } + + // Yield final response with tool calls and metadata + if (toolCallsMap.size > 0 || finishReason || usage) { + const parts: Part[] = []; + let functionCalls: FunctionCall[] | undefined; + + if (toolCallsMap.size > 0) { + // Convert tool calls using ToolStrategy + const toolCallsArray = Array.from(toolCallsMap.values()); + functionCalls = this.toolStrategy.vercelToGemini(toolCallsArray); + + // Add to parts + for (const fc of functionCalls) { + parts.push({ functionCall: fc }); + } + } + + const usageMetadata = this.convertUsage(usage); + + yield Object.setPrototypeOf( + { + candidates: [ + { + content: { + role: 'model', + parts: parts.length > 0 ? parts : [{ text: '' }], + }, + finishReason: this.mapFinishReason(finishReason), + index: 0, + }, + ], + // Top-level functionCalls + ...(functionCalls && functionCalls.length > 0 + ? { functionCalls } + : {}), + usageMetadata, + }, + GenerateContentResponse.prototype, + ); + } + } + + /** + * Convert usage metadata with fallback for undefined fields + */ + private convertUsage(usage: VercelUsage | undefined): + | { + promptTokenCount: number; + candidatesTokenCount: number; + totalTokenCount: number; + } + | undefined { + if (!usage) { + return undefined; + } + + return { + promptTokenCount: usage.promptTokens ?? 0, + candidatesTokenCount: usage.completionTokens ?? 0, + totalTokenCount: usage.totalTokens ?? 0, + }; + } + + /** + * Estimate usage when not provided by model + */ + private estimateUsage(text: string): VercelUsage { + const estimatedTokens = Math.ceil(text.length / 4); + return { + promptTokens: 0, + completionTokens: estimatedTokens, + totalTokens: estimatedTokens, + }; + } + + /** + * Map Vercel finish reasons to Gemini finish reasons + */ + private mapFinishReason( + reason: VercelFinishReason | undefined, + ): FinishReason { + switch (reason) { + case 'stop': + case 'tool-calls': + return FinishReason.STOP; + case 'length': + case 'max-tokens': + return FinishReason.MAX_TOKENS; + case 'content-filter': + return FinishReason.SAFETY; + case 'error': + case 'other': + case 'unknown': + return FinishReason.OTHER; + default: + return FinishReason.STOP; + } + } + + /** + * Create empty response for error cases + */ + private createEmptyResponse(): GenerateContentResponse { + return Object.setPrototypeOf( + { + candidates: [ + { + content: { + role: 'model', + parts: [{ text: '' }], + }, + finishReason: FinishReason.OTHER, + index: 0, + }, + ], + }, + GenerateContentResponse.prototype, + ); + } +} diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.test.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.test.ts new file mode 100644 index 0000000..8c23df6 --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.test.ts @@ -0,0 +1,582 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Unit tests for ToolConversionStrategy + * + * REQUIREMENTS-BASED TESTS + * These tests verify the adapter meets the type contracts between: + * - Gemini SDK (@google/genai): FunctionCall, FunctionDeclaration, Tool + * - Vercel AI SDK (ai): ToolCallPart, VercelTool + * + * Key Type Contracts: + * - FunctionCall.args MUST be Record (object with string keys) + * - FunctionCall.id is OPTIONAL (generated if missing) + * - FunctionDeclaration.description is OPTIONAL (defaults to '') + * - ToolCallPart.args can be ANY JSON value (object, array, primitive, null) + * - Conversion must handle invalid inputs gracefully (no throws) + */ + +import { describe, it as t, expect, beforeEach } from 'vitest'; +import { ToolConversionStrategy } from './tool.js'; +import { Type } from '@google/genai'; +import type { Tool, FunctionDeclaration, Schema } from '@google/genai'; + +describe('ToolConversionStrategy', () => { + let strategy: ToolConversionStrategy; + + beforeEach(() => { + strategy = new ToolConversionStrategy(); + }); + + // ======================================== + // GEMINI → VERCEL (Tool Definitions) + // ======================================== + + describe('geminiToVercel', () => { + t('tests that undefined tools returns undefined', () => { + const result = strategy.geminiToVercel(undefined); + expect(result).toBeUndefined(); + }); + + t('tests that empty tools array returns undefined', () => { + const result = strategy.geminiToVercel([]); + expect(result).toBeUndefined(); + }); + + t('tests that tools without functionDeclarations returns undefined', () => { + const tools = [ + { googleSearch: {} } as unknown as Tool, + { retrieval: {} } as unknown as Tool, + ]; + const result = strategy.geminiToVercel(tools); + expect(result).toBeUndefined(); + }); + + t( + 'tests that single tool with all properties converts to name-keyed object', + () => { + const tools: Tool[] = [ + { + functionDeclarations: [ + { + name: 'get_weather', + description: 'Get weather for a location', + parameters: { + type: Type.OBJECT, + properties: { + location: { type: Type.STRING }, + }, + required: ['location'], + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(tools); + + expect(result).toBeDefined(); + expect(result!['get_weather']).toBeDefined(); + expect(result!['get_weather'].description).toBe( + 'Get weather for a location', + ); + expect(result!['get_weather'].parameters).toBeDefined(); + }, + ); + + t( + 'tests that tool without description uses empty string as default', + () => { + const tools: Tool[] = [ + { + functionDeclarations: [ + { + name: 'simple_tool', + parameters: { type: Type.OBJECT, properties: {} }, + } as FunctionDeclaration, + ], + }, + ]; + + const result = strategy.geminiToVercel(tools); + + expect(result!['simple_tool'].description).toBe(''); + }, + ); + + t( + 'tests that tool without parameters gets normalized with type object', + () => { + const tools: Tool[] = [ + { + functionDeclarations: [ + { + name: 'no_params_tool', + description: 'A tool without parameters', + } as FunctionDeclaration, + ], + }, + ]; + + const result = strategy.geminiToVercel(tools); + + expect(result!['no_params_tool']).toBeDefined(); + expect(result!['no_params_tool'].parameters).toBeDefined(); + }, + ); + + t( + 'tests that multiple tools in one array merge into single name-keyed object', + () => { + const tools: Tool[] = [ + { + functionDeclarations: [ + { + name: 'tool1', + description: 'First', + parameters: { type: Type.OBJECT }, + }, + { + name: 'tool2', + description: 'Second', + parameters: { type: Type.OBJECT }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(tools); + + expect(Object.keys(result!)).toHaveLength(2); + expect(result!['tool1']).toBeDefined(); + expect(result!['tool2']).toBeDefined(); + }, + ); + + t('tests that multiple Tool arrays flatten into one object', () => { + const tools: Tool[] = [ + { + functionDeclarations: [ + { + name: 'tool1', + description: 'First', + parameters: { type: Type.OBJECT }, + }, + ], + }, + { + functionDeclarations: [ + { + name: 'tool2', + description: 'Second', + parameters: { type: Type.OBJECT }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(tools); + + expect(Object.keys(result!)).toHaveLength(2); + expect(result!['tool1']).toBeDefined(); + expect(result!['tool2']).toBeDefined(); + }); + + t( + 'tests that parameters get normalized to include type object for OpenAI compatibility', + () => { + const tools: Tool[] = [ + { + functionDeclarations: [ + { + name: 'test_tool', + description: 'Test', + parameters: { + // Missing 'type' field - should be normalized + properties: { + arg1: { type: Type.STRING }, + }, + } as Schema, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(tools); + + expect(result!['test_tool'].parameters).toBeDefined(); + }, + ); + + t( + 'tests that parameters is wrapped with jsonSchema function from Vercel SDK', + () => { + const tools: Tool[] = [ + { + functionDeclarations: [ + { + name: 'test_tool', + description: 'Test', + parameters: { + type: Type.OBJECT, + properties: { + location: { type: Type.STRING }, + }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(tools); + + // parameters should be defined (wrapped with jsonSchema()) + expect(result!['test_tool'].parameters).toBeDefined(); + expect(typeof result!['test_tool'].parameters).toBe('object'); + }, + ); + + t('tests that nested object parameters preserve full structure', () => { + const tools: Tool[] = [ + { + functionDeclarations: [ + { + name: 'nested_tool', + description: 'Nested params', + parameters: { + type: Type.OBJECT, + properties: { + user: { + type: Type.OBJECT, + properties: { + name: { type: Type.STRING }, + age: { type: Type.NUMBER }, + }, + }, + }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(tools); + + expect(result!['nested_tool']).toBeDefined(); + expect(result!['nested_tool'].parameters).toBeDefined(); + }); + + t('tests that array type parameters convert correctly', () => { + const tools: Tool[] = [ + { + functionDeclarations: [ + { + name: 'array_tool', + description: 'Takes array', + parameters: { + type: Type.OBJECT, + properties: { + tags: { + type: Type.ARRAY, + items: { type: Type.STRING }, + }, + }, + }, + }, + ], + }, + ]; + + const result = strategy.geminiToVercel(tools); + + expect(result!['array_tool']).toBeDefined(); + }); + }); + + // ======================================== + // VERCEL → GEMINI (Tool Calls) + // ======================================== + + describe('vercelToGemini', () => { + t('tests that empty array returns empty array', () => { + const result = strategy.vercelToGemini([]); + expect(result).toEqual([]); + }); + + t('tests that valid tool call with object input converts correctly', () => { + const toolCalls = [ + { + toolCallId: 'call_123', + toolName: 'get_weather', + args: { location: 'Tokyo', units: 'celsius' }, + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result).toHaveLength(1); + expect(result[0].id).toBe('call_123'); + expect(result[0].name).toBe('get_weather'); + expect(result[0].args).toEqual({ location: 'Tokyo', units: 'celsius' }); + }); + + t('tests that tool call with empty object input converts correctly', () => { + const toolCalls = [ + { + toolCallId: 'call_456', + toolName: 'simple_tool', + args: {}, + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result[0].args).toEqual({}); + }); + + // CRITICAL: FunctionCall.args MUST be Record + // Arrays violate this type contract and must be converted to {} + + t( + 'tests that tool call with array input converts to empty object per type contract', + () => { + const toolCalls = [ + { + toolCallId: 'call_arr', + toolName: 'invalid_array_tool', + args: [1, 2, 3], + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + // Arrays violate Record type contract + // Must be converted to {} to satisfy FunctionCall.args type + expect(result[0].args).toEqual({}); + expect(Array.isArray(result[0].args)).toBe(false); + }, + ); + + t('tests that tool call with null input converts to empty object', () => { + const toolCalls = [ + { + toolCallId: 'call_null', + toolName: 'null_tool', + args: null, + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result[0].args).toEqual({}); + }); + + t( + 'tests that tool call with undefined input converts to empty object', + () => { + const toolCalls = [ + { + toolCallId: 'call_undef', + toolName: 'undef_tool', + args: undefined, + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result[0].args).toEqual({}); + }, + ); + + t('tests that tool call with string input converts to empty object', () => { + const toolCalls = [ + { + toolCallId: 'call_str', + toolName: 'str_tool', + args: 'not an object', + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result[0].args).toEqual({}); + }); + + t('tests that tool call with number input converts to empty object', () => { + const toolCalls = [ + { + toolCallId: 'call_num', + toolName: 'num_tool', + args: 42, + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result[0].args).toEqual({}); + }); + + t( + 'tests that tool call with boolean input converts to empty object', + () => { + const toolCalls = [ + { + toolCallId: 'call_bool', + toolName: 'bool_tool', + args: true, + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result[0].args).toEqual({}); + }, + ); + + t('tests that tool call with nested object preserves structure', () => { + const toolCalls = [ + { + toolCallId: 'call_nested', + toolName: 'nested_tool', + args: { + user: { + name: 'Alice', + address: { + city: 'Tokyo', + country: 'Japan', + }, + }, + timestamp: 1234567890, + }, + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result[0].args).toEqual({ + user: { + name: 'Alice', + address: { + city: 'Tokyo', + country: 'Japan', + }, + }, + timestamp: 1234567890, + }); + }); + + t('tests that multiple tool calls all convert', () => { + const toolCalls = [ + { toolCallId: 'call_1', toolName: 'tool1', args: { arg: 'val1' } }, + { toolCallId: 'call_2', toolName: 'tool2', args: { arg: 'val2' } }, + { toolCallId: 'call_3', toolName: 'tool3', args: {} }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result).toHaveLength(3); + expect(result[0].name).toBe('tool1'); + expect(result[1].name).toBe('tool2'); + expect(result[2].name).toBe('tool3'); + }); + + t('tests that tool call ID with special characters is preserved', () => { + const toolCalls = [ + { + toolCallId: 'call_123-abc_XYZ.v2', + toolName: 'test_tool', + args: {}, + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result[0].id).toBe('call_123-abc_XYZ.v2'); + }); + + // Error handling: Should return fallback, NOT throw + + t( + 'tests that missing toolCallId returns fallback structure without throwing', + () => { + const toolCalls = [ + { + toolName: 'missing_id_tool', + args: { test: true }, + } as unknown, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + // Should not throw, returns fallback + expect(result).toHaveLength(1); + expect(result[0].id).toBe('invalid_0'); + expect(result[0].name).toBe('unknown'); + expect(result[0].args).toEqual({}); + }, + ); + + t( + 'tests that missing toolName returns fallback structure without throwing', + () => { + const toolCalls = [ + { + toolCallId: 'call_no_name', + args: { test: true }, + } as unknown, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + // Should not throw, returns fallback + expect(result).toHaveLength(1); + expect(result[0].id).toBe('invalid_0'); + expect(result[0].name).toBe('unknown'); + expect(result[0].args).toEqual({}); + }, + ); + + t( + 'tests that completely invalid tool call returns fallback structure', + () => { + const toolCalls = [{ invalid: 'data', random: 123 } as unknown]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result).toHaveLength(1); + expect(result[0].id).toBe('invalid_0'); + expect(result[0].name).toBe('unknown'); + expect(result[0].args).toEqual({}); + }, + ); + + t( + 'tests that mix of valid and invalid tool calls all return valid structures', + () => { + const toolCalls = [ + { toolCallId: 'call_1', toolName: 'valid_tool', args: { test: 1 } }, + { invalid: 'data' } as unknown, + { + toolCallId: 'call_2', + toolName: 'another_valid', + args: { test: 2 }, + }, + ]; + + const result = strategy.vercelToGemini(toolCalls); + + expect(result).toHaveLength(3); + expect(result[0].id).toBe('call_1'); + expect(result[0].name).toBe('valid_tool'); + expect(result[1].id).toBe('invalid_1'); + expect(result[1].name).toBe('unknown'); + expect(result[2].id).toBe('call_2'); + expect(result[2].name).toBe('another_valid'); + }, + ); + }); +}); diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts new file mode 100644 index 0000000..2eaa872 --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts @@ -0,0 +1,225 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Tool Conversion Strategy + * Converts tool definitions and tool calls between Gemini and Vercel formats + */ + +import type { + FunctionCall, + FunctionDeclaration, + VercelTool, +} from '../types.js'; +import { jsonSchema, VercelToolCallSchema } from '../types.js'; +import { ConversionError } from '../errors.js'; +import type { ToolListUnion } from '@google/genai'; + +export class ToolConversionStrategy { + /** + * Normalize schema for OpenAI strict mode compliance + * OpenAI requires: + * 1. additionalProperties: false on ALL objects + * 2. required: [...] array listing ALL properties (makes everything required) + */ + private normalizeForOpenAI(schema: Record): Record { + const result = { ...schema }; + + // Apply OpenAI requirements for object types + if (result.type === 'object') { + // 1. Add additionalProperties: false + if (result.additionalProperties === undefined) { + result.additionalProperties = false; + } + + // 2. Add required array with ALL property keys + if (result.properties && typeof result.properties === 'object') { + const propertyKeys = Object.keys(result.properties); + if (propertyKeys.length > 0) { + // Merge with existing required array (if any) and ensure all keys are included + const existingRequired = Array.isArray(result.required) ? result.required : []; + const allRequired = Array.from(new Set([...existingRequired, ...propertyKeys])); + result.required = allRequired; + } + } + } + + // Recursively process properties + if (result.properties && typeof result.properties === 'object') { + const newProperties: Record = {}; + for (const [key, value] of Object.entries(result.properties)) { + if (value && typeof value === 'object') { + newProperties[key] = this.normalizeForOpenAI(value as Record); + } else { + newProperties[key] = value; + } + } + result.properties = newProperties; + } + + // Recursively process items (for arrays) + if (result.items && typeof result.items === 'object' && !Array.isArray(result.items)) { + result.items = this.normalizeForOpenAI(result.items as Record); + } + + // Recursively process anyOf, allOf, oneOf + if (Array.isArray(result.anyOf)) { + result.anyOf = result.anyOf.map(item => { + if (item && typeof item === 'object') { + return this.normalizeForOpenAI(item as Record); + } + return item; + }); + } + + if (Array.isArray(result.allOf)) { + result.allOf = result.allOf.map(item => { + if (item && typeof item === 'object') { + return this.normalizeForOpenAI(item as Record); + } + return item; + }); + } + + if (Array.isArray(result.oneOf)) { + result.oneOf = result.oneOf.map(item => { + if (item && typeof item === 'object') { + return this.normalizeForOpenAI(item as Record); + } + return item; + }); + } + + return result; + } + + /** + * Convert Gemini tool definitions to Vercel format + * + * @param tools - Array of Gemini Tool/CallableTool objects + * @returns Record mapping tool names to Vercel tool definitions + */ + geminiToVercel( + tools: ToolListUnion | undefined, + ): Record | undefined { + if (!tools || tools.length === 0) { + return undefined; + } + + // Extract function declarations from all tools + // Filter for Tool types (not CallableTool) + const declarations: FunctionDeclaration[] = []; + for (const tool of tools) { + // Check if this is a Tool with functionDeclarations (not CallableTool) + if ('functionDeclarations' in tool && tool.functionDeclarations) { + declarations.push(...tool.functionDeclarations); + } + } + + if (declarations.length === 0) { + return undefined; + } + + const vercelTools: Record = {}; + + for (const func of declarations) { + // Validate required fields + if (!func.name) { + throw new ConversionError( + 'Tool definition missing required name field', + { + stage: 'tool', + operation: 'geminiToVercel', + input: { hasDescription: !!func.description }, + }, + ); + } + + // Get parameters from either parametersJsonSchema (JSON Schema) or parameters (Gemini Schema) + // Gemini SDK provides both, they are mutually exclusive + // parametersJsonSchema is typed as 'unknown', need to validate it's an object + let rawParameters: Record; + + if (func.parametersJsonSchema !== undefined) { + // Prefer parametersJsonSchema (standard JSON Schema format) + if (typeof func.parametersJsonSchema === 'object' && func.parametersJsonSchema !== null) { + rawParameters = func.parametersJsonSchema as Record; + } else { + throw new ConversionError( + `Tool ${func.name}: parametersJsonSchema must be an object`, + { stage: 'tool', operation: 'geminiToVercel', input: { parametersJsonSchema: func.parametersJsonSchema } } + ); + } + } else if (func.parameters !== undefined) { + // Fallback to parameters (Gemini Schema format) + rawParameters = func.parameters as unknown as Record; + } else { + // No parameters defined + rawParameters = {}; + } + + const parametersWithType = { + type: 'object' as const, + properties: {}, + ...rawParameters, + }; + + const normalizedParameters = this.normalizeForOpenAI(parametersWithType); + + const wrappedParams = jsonSchema( + normalizedParameters as Parameters[0], + ); + + vercelTools[func.name] = { + description: func.description || '', + inputSchema: wrappedParams, + }; + } + + return Object.keys(vercelTools).length > 0 ? vercelTools : undefined; + } + + /** + * Convert Vercel tool calls to Gemini function calls + * + * @param toolCalls - Array of tool calls from Vercel response + * @returns Array of Gemini FunctionCall objects + */ + vercelToGemini(toolCalls: readonly unknown[]): FunctionCall[] { + if (!toolCalls || toolCalls.length === 0) { + return []; + } + + return toolCalls.map((tc, index) => { + const parsed = VercelToolCallSchema.safeParse(tc); + + if (!parsed.success) { + return { + id: `invalid_${index}`, + name: 'unknown', + args: {}, + }; + } + + const validated = parsed.data; + + // Convert to Gemini format + // SDK uses 'input' property matching ToolCallPart interface (AI SDK v5) + // CRITICAL: FunctionCall.args must be Record + // Arrays violate this type contract and must be converted to {} + return { + id: validated.toolCallId, + name: validated.toolName, + args: + typeof validated.input === 'object' && + validated.input !== null && + !Array.isArray(validated.input) + ? (validated.input as Record) + : {}, + }; + }); + } +} diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts new file mode 100644 index 0000000..48fe056 --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts @@ -0,0 +1,239 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Type Definitions for Vercel AI Adapter + * Single source of truth for all types + Zod schemas + */ + +import { z } from 'zod'; +import { jsonSchema } from 'ai'; + +// Re-export for use in strategies +export { jsonSchema }; + +// === Re-export SDK Types === + +// Vercel AI SDK +export type { CoreMessage } from 'ai'; +export type { LanguageModelV2ToolResultOutput } from '@ai-sdk/provider'; + +// Gemini SDK +export type { + Part, + FunctionCall, + FunctionDeclaration, + FunctionResponse, + Tool, + Content, + GenerateContentResponse, + FinishReason, +} from '@google/genai'; + +// === Vercel SDK Runtime Shapes (What We Receive) === + +/** + * Tool call from generateText result + * Per SDK docs: uses 'input' property matching ToolCallPart interface + */ +export const VercelToolCallSchema = z.object({ + toolCallId: z.string(), + toolName: z.string(), + input: z.unknown(), // Matches ToolCallPart interface +}); + +export type VercelToolCall = z.infer; + +/** + * Usage metadata from result + * All fields can be undefined per SDK types + * Uses actual SDK property names: promptTokens, completionTokens, totalTokens + */ +export const VercelUsageSchema = z.object({ + promptTokens: z.number().optional(), + completionTokens: z.number().optional(), + totalTokens: z.number().optional(), +}); + +export type VercelUsage = z.infer; + +/** + * Finish reason from Vercel SDK + */ +export const VercelFinishReasonSchema = z.enum([ + 'stop', + 'length', + 'max-tokens', + 'tool-calls', + 'content-filter', + 'error', + 'other', + 'unknown', +]); + +export type VercelFinishReason = z.infer; + +/** + * GenerateText result shape + * Only the fields we actually use + */ +export const VercelGenerateTextResultSchema = z.object({ + text: z.string(), + toolCalls: z.array(VercelToolCallSchema).optional(), + finishReason: VercelFinishReasonSchema.optional(), + usage: VercelUsageSchema.optional(), +}); + +export type VercelGenerateTextResult = z.infer< + typeof VercelGenerateTextResultSchema +>; + +// === Stream Chunk Schemas === + +/** + * Text delta chunk from fullStream + * Note: In AI SDK v5, property name is 'text' (was 'textDelta' in v4) + */ +export const VercelTextDeltaChunkSchema = z.object({ + type: z.literal('text-delta'), + text: z.string(), +}); + +/** + * Tool call chunk from fullStream + * Note: SDK uses 'input' property matching ToolCallPart interface + */ +export const VercelToolCallChunkSchema = z.object({ + type: z.literal('tool-call'), + toolCallId: z.string(), + toolName: z.string(), + input: z.unknown(), // SDK uses 'input' for both stream chunks and result.toolCalls +}); + +/** + * Finish chunk from fullStream + */ +export const VercelFinishChunkSchema = z.object({ + type: z.literal('finish'), + finishReason: VercelFinishReasonSchema.optional(), +}); + +/** + * Union of stream chunks we process + * (SDK emits many other types we ignore) + */ +export const VercelStreamChunkSchema = z.discriminatedUnion('type', [ + VercelTextDeltaChunkSchema, + VercelToolCallChunkSchema, + VercelFinishChunkSchema, +]); + +export type VercelTextDeltaChunk = z.infer; +export type VercelToolCallChunk = z.infer; +export type VercelFinishChunk = z.infer; +export type VercelStreamChunk = z.infer; + +// === Message Content Parts (What We Build for Vercel) === + +/** + * Text part in message content + */ +export interface VercelTextPart { + readonly type: 'text'; + readonly text: string; +} + +/** + * Tool call part in assistant message + * Uses 'input' property per ToolCallPart interface + */ +export interface VercelToolCallPart { + readonly type: 'tool-call'; + readonly toolCallId: string; + readonly toolName: string; + readonly input: unknown; // SDK uses 'input' for message parts +} + +/** + * Tool result part in tool message + * Matches Vercel AI SDK v5's ToolResultPart interface + * Note: output must be structured in v5 (not a raw value) + */ +export interface VercelToolResultPart { + readonly type: 'tool-result'; + readonly toolCallId: string; + readonly toolName: string; + readonly output: LanguageModelV2ToolResultOutput; // v5 requires structured output +} + +/** + * Image part in message content + * Matches Vercel AI SDK's ImagePart interface + * + * Image data can be: + * - Base64 data URL: "data:image/png;base64,..." + * - Regular URL: URL object or string + * - Binary data: Uint8Array, ArrayBuffer, or Buffer + */ +export interface VercelImagePart { + readonly type: 'image'; + readonly image: string | URL | Uint8Array | ArrayBuffer | Buffer; + readonly mediaType?: string; +} + +/** + * Content part - union of all part types + */ +export type VercelContentPart = + | VercelTextPart + | VercelToolCallPart + | VercelToolResultPart + | VercelImagePart; + +// === Tool Definition (What We Build for Vercel) === + +/** + * Vercel tool definition + * inputSchema must be wrapped with jsonSchema() function + * Note: AI SDK v5 uses 'inputSchema' (v4 used 'parameters') + */ +export interface VercelTool { + readonly description: string; + readonly inputSchema: ReturnType; + readonly execute?: (args: Record) => Promise; +} + +// === Helper Types === + +/** + * Hono Stream interface for direct streaming + * Minimal interface to avoid Hono dependency in adapter + */ +export interface HonoSSEStream { + write(data: string): Promise; +} + +/** + * Configuration for Vercel AI adapter + */ +export interface VercelAIConfig { + model: string; + apiKeys?: { + anthropic?: string; + openai?: string; + google?: string; + openrouter?: string; + azure?: string; + }; + azureResourceName?: string; + ollamaBaseUrl?: string; + lmstudioBaseUrl?: string; + awsRegion?: string; + awsAccessKeyId?: string; + awsSecretAccessKey?: string; + awsSessionToken?: string; + honoStream?: HonoSSEStream; +} diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/utils/index.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/utils/index.ts new file mode 100644 index 0000000..52f711c --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/utils/index.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Utilities barrel export + * Single entry point for all utility functions + */ + +export { + isTextPart, + isFunctionCallPart, + isFunctionResponsePart, + isInlineDataPart, + isFileDataPart, + isImageMimeType, +} from './type-guards.js'; diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/utils/type-guards.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/utils/type-guards.ts new file mode 100644 index 0000000..1d7b11b --- /dev/null +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/utils/type-guards.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Type guards for Gemini Part types + * Enable TypeScript to narrow types for type safety + */ + +import type { Part, FunctionCall, FunctionResponse } from '@google/genai'; + +/** + * Check if part contains text + */ +export function isTextPart(part: Part): part is Part & { text: string } { + return 'text' in part && typeof part.text === 'string'; +} + +/** + * Check if part contains function call + */ +export function isFunctionCallPart( + part: Part, +): part is Part & { functionCall: FunctionCall } { + return 'functionCall' in part && part.functionCall !== undefined; +} + +/** + * Check if part contains function response + */ +export function isFunctionResponsePart( + part: Part, +): part is Part & { functionResponse: FunctionResponse } { + return 'functionResponse' in part && part.functionResponse !== undefined; +} + +/** + * Check if part contains inline data (images, etc.) + */ +export function isInlineDataPart( + part: Part, +): part is Part & { inlineData: { mimeType: string; data: string } } { + return ( + 'inlineData' in part && + typeof part.inlineData === 'object' && + part.inlineData !== null && + 'mimeType' in part.inlineData && + 'data' in part.inlineData + ); +} + +/** + * Check if part contains file data + */ +export function isFileDataPart( + part: Part, +): part is Part & { fileData: { mimeType: string; fileUri: string } } { + return ( + 'fileData' in part && + typeof part.fileData === 'object' && + part.fileData !== null && + 'mimeType' in part.fileData && + 'fileUri' in part.fileData + ); +} + +/** + * Check if mime type is an image + */ +export function isImageMimeType(mimeType: string): boolean { + return mimeType.startsWith('image/'); +} From add3f78af1bf6858b50bc4c5a45e9c276f0e4b83 Mon Sep 17 00:00:00 2001 From: shivammittal274 Date: Tue, 25 Nov 2025 23:37:06 +0530 Subject: [PATCH 2/7] tests fixed based upon v5 --- .../strategies/response.test.ts | 52 +++++++++---------- .../strategies/tool.test.ts | 48 ++++++++--------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.test.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.test.ts index c8ee08d..f600130 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.test.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.test.ts @@ -45,8 +45,8 @@ describe('ResponseConversionStrategy', () => { text: 'Hello world', finishReason: 'stop' as const, usage: { - inputTokens: 10, - outputTokens: 5, + promptTokens: 10, + completionTokens: 5, totalTokens: 15, }, }; @@ -68,8 +68,8 @@ describe('ResponseConversionStrategy', () => { const vercelResult = { text: 'Test', usage: { - inputTokens: 100, - outputTokens: 50, + promptTokens: 100, + completionTokens: 50, totalTokens: 150, }, }; @@ -91,7 +91,7 @@ describe('ResponseConversionStrategy', () => { { toolCallId: 'call_123', toolName: 'get_weather', - args: { location: 'Tokyo' }, + input: { location: 'Tokyo' }, }, ], finishReason: 'tool-calls' as const, @@ -117,7 +117,7 @@ describe('ResponseConversionStrategy', () => { { toolCallId: 'call_456', toolName: 'search', - args: { query: 'test' }, + input: { query: 'test' }, }, ], }; @@ -143,7 +143,7 @@ describe('ResponseConversionStrategy', () => { { toolCallId: 'call_789', toolName: 'get_weather', - args: { location: 'Paris' }, + input: { location: 'Paris' }, }, ], }; @@ -163,8 +163,8 @@ describe('ResponseConversionStrategy', () => { const vercelResult = { text: '', toolCalls: [ - { toolCallId: 'call_1', toolName: 'tool1', args: { arg: 'val1' } }, - { toolCallId: 'call_2', toolName: 'tool2', args: { arg: 'val2' } }, + { toolCallId: 'call_1', toolName: 'tool1', input: { arg: 'val1' } }, + { toolCallId: 'call_2', toolName: 'tool2', input: { arg: 'val2' } }, ], }; @@ -201,8 +201,8 @@ describe('ResponseConversionStrategy', () => { const vercelResult = { text: 'Test', usage: { - inputTokens: undefined, - outputTokens: 5, + promptTokens: undefined, + completionTokens: 5, totalTokens: undefined, }, }; @@ -227,7 +227,7 @@ describe('ResponseConversionStrategy', () => { t('tests that tool-calls finish reason maps to STOP', () => { const result = strategy.vercelToGemini({ text: '', - toolCalls: [{ toolCallId: 'call_1', toolName: 'tool', args: {} }], + toolCalls: [{ toolCallId: 'call_1', toolName: 'tool', input: {} }], finishReason: 'tool-calls' as const, }); expect(result.candidates![0].finishReason!).toBe(FinishReason.STOP); @@ -313,8 +313,8 @@ describe('ResponseConversionStrategy', () => { 'tests that stream with text-delta chunks yields immediately', async () => { const stream = (async function* () { - yield { type: 'text-delta', textDelta: 'Hello' }; - yield { type: 'text-delta', textDelta: ' world' }; + yield { type: 'text-delta', text: 'Hello' }; + yield { type: 'text-delta', text: ' world' }; yield { type: 'finish', finishReason: 'stop' as const }; })(); @@ -344,7 +344,7 @@ describe('ResponseConversionStrategy', () => { type: 'tool-call', toolCallId: 'call_123', toolName: 'get_weather', - args: { location: 'Tokyo' }, + input: { location: 'Tokyo' }, }; yield { type: 'finish', finishReason: 'tool-calls' as const }; })(); @@ -372,13 +372,13 @@ describe('ResponseConversionStrategy', () => { type: 'tool-call', toolCallId: 'call_1', toolName: 'tool1', - args: { arg: 'val1' }, + input: { arg: 'val1' }, }; yield { type: 'tool-call', toolCallId: 'call_2', toolName: 'tool2', - args: { arg: 'val2' }, + input: { arg: 'val2' }, }; yield { type: 'finish', finishReason: 'tool-calls' as const }; })(); @@ -397,12 +397,12 @@ describe('ResponseConversionStrategy', () => { t('tests that stream with text and tool calls yields both', async () => { const stream = (async function* () { - yield { type: 'text-delta', textDelta: 'Searching...' }; + yield { type: 'text-delta', text: 'Searching...' }; yield { type: 'tool-call', toolCallId: 'call_search', toolName: 'search', - args: { query: 'test' }, + input: { query: 'test' }, }; yield { type: 'finish', finishReason: 'tool-calls' as const }; })(); @@ -428,7 +428,7 @@ describe('ResponseConversionStrategy', () => { async () => { const stream = (async function* () { yield { type: 'start' } as unknown; // Unknown type - yield { type: 'text-delta', textDelta: 'Hello' }; + yield { type: 'text-delta', text: 'Hello' }; yield { type: 'step-finish' } as unknown; // Unknown type yield { type: 'finish', finishReason: 'stop' as const }; })(); @@ -447,7 +447,7 @@ describe('ResponseConversionStrategy', () => { t('tests that stream with empty text-delta still yields', async () => { const stream = (async function* () { - yield { type: 'text-delta', textDelta: '' }; + yield { type: 'text-delta', text: '' }; yield { type: 'finish', finishReason: 'stop' as const }; })(); @@ -464,7 +464,7 @@ describe('ResponseConversionStrategy', () => { t('tests that stream without finish reason still completes', async () => { const stream = (async function* () { - yield { type: 'text-delta', textDelta: 'Test' }; + yield { type: 'text-delta', text: 'Test' }; // No finish chunk })(); @@ -482,7 +482,7 @@ describe('ResponseConversionStrategy', () => { 'tests that stream with getUsage error uses estimation fallback', async () => { const stream = (async function* () { - yield { type: 'text-delta', textDelta: 'Test message here' }; + yield { type: 'text-delta', text: 'Test message here' }; yield { type: 'finish', finishReason: 'stop' as const }; })(); @@ -525,13 +525,13 @@ describe('ResponseConversionStrategy', () => { 'tests that stream usage metadata is included in final chunk', async () => { const stream = (async function* () { - yield { type: 'text-delta', textDelta: 'Test' }; + yield { type: 'text-delta', text: 'Test' }; yield { type: 'finish', finishReason: 'stop' as const }; })(); const getUsage = async () => ({ - inputTokens: 10, - outputTokens: 5, + promptTokens: 10, + completionTokens: 5, totalTokens: 15, }); diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.test.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.test.ts index 8c23df6..dda53fb 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.test.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.test.ts @@ -84,7 +84,7 @@ describe('ToolConversionStrategy', () => { expect(result!['get_weather'].description).toBe( 'Get weather for a location', ); - expect(result!['get_weather'].parameters).toBeDefined(); + expect(result!['get_weather'].inputSchema).toBeDefined(); }, ); @@ -125,7 +125,7 @@ describe('ToolConversionStrategy', () => { const result = strategy.geminiToVercel(tools); expect(result!['no_params_tool']).toBeDefined(); - expect(result!['no_params_tool'].parameters).toBeDefined(); + expect(result!['no_params_tool'].inputSchema).toBeDefined(); }, ); @@ -208,7 +208,7 @@ describe('ToolConversionStrategy', () => { const result = strategy.geminiToVercel(tools); - expect(result!['test_tool'].parameters).toBeDefined(); + expect(result!['test_tool'].inputSchema).toBeDefined(); }, ); @@ -234,9 +234,9 @@ describe('ToolConversionStrategy', () => { const result = strategy.geminiToVercel(tools); - // parameters should be defined (wrapped with jsonSchema()) - expect(result!['test_tool'].parameters).toBeDefined(); - expect(typeof result!['test_tool'].parameters).toBe('object'); + // inputSchema should be defined (wrapped with jsonSchema()) + expect(result!['test_tool'].inputSchema).toBeDefined(); + expect(typeof result!['test_tool'].inputSchema).toBe('object'); }, ); @@ -267,7 +267,7 @@ describe('ToolConversionStrategy', () => { const result = strategy.geminiToVercel(tools); expect(result!['nested_tool']).toBeDefined(); - expect(result!['nested_tool'].parameters).toBeDefined(); + expect(result!['nested_tool'].inputSchema).toBeDefined(); }); t('tests that array type parameters convert correctly', () => { @@ -312,7 +312,7 @@ describe('ToolConversionStrategy', () => { { toolCallId: 'call_123', toolName: 'get_weather', - args: { location: 'Tokyo', units: 'celsius' }, + input: { location: 'Tokyo', units: 'celsius' }, }, ]; @@ -329,7 +329,7 @@ describe('ToolConversionStrategy', () => { { toolCallId: 'call_456', toolName: 'simple_tool', - args: {}, + input: {}, }, ]; @@ -348,7 +348,7 @@ describe('ToolConversionStrategy', () => { { toolCallId: 'call_arr', toolName: 'invalid_array_tool', - args: [1, 2, 3], + input: [1, 2, 3], }, ]; @@ -366,7 +366,7 @@ describe('ToolConversionStrategy', () => { { toolCallId: 'call_null', toolName: 'null_tool', - args: null, + input: null, }, ]; @@ -382,7 +382,7 @@ describe('ToolConversionStrategy', () => { { toolCallId: 'call_undef', toolName: 'undef_tool', - args: undefined, + input: undefined, }, ]; @@ -397,7 +397,7 @@ describe('ToolConversionStrategy', () => { { toolCallId: 'call_str', toolName: 'str_tool', - args: 'not an object', + input: 'not an object', }, ]; @@ -411,7 +411,7 @@ describe('ToolConversionStrategy', () => { { toolCallId: 'call_num', toolName: 'num_tool', - args: 42, + input: 42, }, ]; @@ -427,7 +427,7 @@ describe('ToolConversionStrategy', () => { { toolCallId: 'call_bool', toolName: 'bool_tool', - args: true, + input: true, }, ]; @@ -442,7 +442,7 @@ describe('ToolConversionStrategy', () => { { toolCallId: 'call_nested', toolName: 'nested_tool', - args: { + input: { user: { name: 'Alice', address: { @@ -471,9 +471,9 @@ describe('ToolConversionStrategy', () => { t('tests that multiple tool calls all convert', () => { const toolCalls = [ - { toolCallId: 'call_1', toolName: 'tool1', args: { arg: 'val1' } }, - { toolCallId: 'call_2', toolName: 'tool2', args: { arg: 'val2' } }, - { toolCallId: 'call_3', toolName: 'tool3', args: {} }, + { toolCallId: 'call_1', toolName: 'tool1', input: { arg: 'val1' } }, + { toolCallId: 'call_2', toolName: 'tool2', input: { arg: 'val2' } }, + { toolCallId: 'call_3', toolName: 'tool3', input: {} }, ]; const result = strategy.vercelToGemini(toolCalls); @@ -489,7 +489,7 @@ describe('ToolConversionStrategy', () => { { toolCallId: 'call_123-abc_XYZ.v2', toolName: 'test_tool', - args: {}, + input: {}, }, ]; @@ -506,7 +506,7 @@ describe('ToolConversionStrategy', () => { const toolCalls = [ { toolName: 'missing_id_tool', - args: { test: true }, + input: { test: true }, } as unknown, ]; @@ -526,7 +526,7 @@ describe('ToolConversionStrategy', () => { const toolCalls = [ { toolCallId: 'call_no_name', - args: { test: true }, + input: { test: true }, } as unknown, ]; @@ -558,12 +558,12 @@ describe('ToolConversionStrategy', () => { 'tests that mix of valid and invalid tool calls all return valid structures', () => { const toolCalls = [ - { toolCallId: 'call_1', toolName: 'valid_tool', args: { test: 1 } }, + { toolCallId: 'call_1', toolName: 'valid_tool', input: { test: 1 } }, { invalid: 'data' } as unknown, { toolCallId: 'call_2', toolName: 'another_valid', - args: { test: 2 }, + input: { test: 2 }, }, ]; From 65252b00b494a6f878438c1dfde3d5aafc8c9a09 Mon Sep 17 00:00:00 2001 From: shivammittal274 Date: Tue, 25 Nov 2025 23:53:39 +0530 Subject: [PATCH 3/7] remove logic for normalisation for openai (not needed) --- .../strategies/response.ts | 117 ++++++++---------- .../strategies/tool.ts | 79 +----------- 2 files changed, 53 insertions(+), 143 deletions(-) diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts index 28a691a..83bfb06 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts @@ -65,26 +65,22 @@ export class ResponseConversionStrategy { // Handle usage metadata const usageMetadata = this.convertUsage(validated.usage); - // Create response with Object.setPrototypeOf pattern - // This allows setting readonly functionCalls property - return Object.setPrototypeOf( - { - candidates: [ - { - content: { - role: 'model', - parts, - }, - finishReason: this.mapFinishReason(validated.finishReason), - index: 0, + // Create response - testing without Object.setPrototypeOf + return { + candidates: [ + { + content: { + role: 'model', + parts, }, - ], - // CRITICAL: Top-level functionCalls for turn.ts compatibility - ...(functionCalls && functionCalls.length > 0 ? { functionCalls } : {}), - usageMetadata, - }, - GenerateContentResponse.prototype, - ); + finishReason: this.mapFinishReason(validated.finishReason), + index: 0, + }, + ], + // CRITICAL: Top-level functionCalls for turn.ts compatibility + ...(functionCalls && functionCalls.length > 0 ? { functionCalls } : {}), + usageMetadata, + } as GenerateContentResponse; } /** @@ -149,20 +145,17 @@ export class ResponseConversionStrategy { } } - yield Object.setPrototypeOf( - { - candidates: [ - { - content: { - role: 'model', - parts: [{ text: delta }], - }, - index: 0, + yield { + candidates: [ + { + content: { + role: 'model', + parts: [{ text: delta }], }, - ], - }, - GenerateContentResponse.prototype, - ); + index: 0, + }, + ], + } as GenerateContentResponse; } else if (chunk.type === 'tool-call') { // Emit v5 SSE format to frontend: tool-call event if (honoStream) { @@ -238,26 +231,23 @@ export class ResponseConversionStrategy { const usageMetadata = this.convertUsage(usage); - yield Object.setPrototypeOf( - { - candidates: [ - { - content: { - role: 'model', - parts: parts.length > 0 ? parts : [{ text: '' }], - }, - finishReason: this.mapFinishReason(finishReason), - index: 0, + yield { + candidates: [ + { + content: { + role: 'model', + parts: parts.length > 0 ? parts : [{ text: '' }], }, - ], - // Top-level functionCalls - ...(functionCalls && functionCalls.length > 0 - ? { functionCalls } - : {}), - usageMetadata, - }, - GenerateContentResponse.prototype, - ); + finishReason: this.mapFinishReason(finishReason), + index: 0, + }, + ], + // Top-level functionCalls + ...(functionCalls && functionCalls.length > 0 + ? { functionCalls } + : {}), + usageMetadata, + } as GenerateContentResponse; } } @@ -322,20 +312,17 @@ export class ResponseConversionStrategy { * Create empty response for error cases */ private createEmptyResponse(): GenerateContentResponse { - return Object.setPrototypeOf( - { - candidates: [ - { - content: { - role: 'model', - parts: [{ text: '' }], - }, - finishReason: FinishReason.OTHER, - index: 0, + return { + candidates: [ + { + content: { + role: 'model', + parts: [{ text: '' }], }, - ], - }, - GenerateContentResponse.prototype, - ); + finishReason: FinishReason.OTHER, + index: 0, + }, + ], + } as GenerateContentResponse; } } diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts index 2eaa872..b8c7f91 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts @@ -19,83 +19,6 @@ import { ConversionError } from '../errors.js'; import type { ToolListUnion } from '@google/genai'; export class ToolConversionStrategy { - /** - * Normalize schema for OpenAI strict mode compliance - * OpenAI requires: - * 1. additionalProperties: false on ALL objects - * 2. required: [...] array listing ALL properties (makes everything required) - */ - private normalizeForOpenAI(schema: Record): Record { - const result = { ...schema }; - - // Apply OpenAI requirements for object types - if (result.type === 'object') { - // 1. Add additionalProperties: false - if (result.additionalProperties === undefined) { - result.additionalProperties = false; - } - - // 2. Add required array with ALL property keys - if (result.properties && typeof result.properties === 'object') { - const propertyKeys = Object.keys(result.properties); - if (propertyKeys.length > 0) { - // Merge with existing required array (if any) and ensure all keys are included - const existingRequired = Array.isArray(result.required) ? result.required : []; - const allRequired = Array.from(new Set([...existingRequired, ...propertyKeys])); - result.required = allRequired; - } - } - } - - // Recursively process properties - if (result.properties && typeof result.properties === 'object') { - const newProperties: Record = {}; - for (const [key, value] of Object.entries(result.properties)) { - if (value && typeof value === 'object') { - newProperties[key] = this.normalizeForOpenAI(value as Record); - } else { - newProperties[key] = value; - } - } - result.properties = newProperties; - } - - // Recursively process items (for arrays) - if (result.items && typeof result.items === 'object' && !Array.isArray(result.items)) { - result.items = this.normalizeForOpenAI(result.items as Record); - } - - // Recursively process anyOf, allOf, oneOf - if (Array.isArray(result.anyOf)) { - result.anyOf = result.anyOf.map(item => { - if (item && typeof item === 'object') { - return this.normalizeForOpenAI(item as Record); - } - return item; - }); - } - - if (Array.isArray(result.allOf)) { - result.allOf = result.allOf.map(item => { - if (item && typeof item === 'object') { - return this.normalizeForOpenAI(item as Record); - } - return item; - }); - } - - if (Array.isArray(result.oneOf)) { - result.oneOf = result.oneOf.map(item => { - if (item && typeof item === 'object') { - return this.normalizeForOpenAI(item as Record); - } - return item; - }); - } - - return result; - } - /** * Convert Gemini tool definitions to Vercel format * @@ -167,7 +90,7 @@ export class ToolConversionStrategy { ...rawParameters, }; - const normalizedParameters = this.normalizeForOpenAI(parametersWithType); + const normalizedParameters = parametersWithType; const wrappedParams = jsonSchema( normalizedParameters as Parameters[0], From 0765f9bcae532b1cba71b948caad81f75756b338 Mon Sep 17 00:00:00 2001 From: shivammittal274 Date: Wed, 26 Nov 2025 00:30:10 +0530 Subject: [PATCH 4/7] tests fixed based upon v5 --- .../agent/gemini-vercel-sdk-adapter/index.ts | 79 +++++++++++-------- .../agent/gemini-vercel-sdk-adapter/types.ts | 44 +++++++---- 2 files changed, 77 insertions(+), 46 deletions(-) diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts index 5878521..8323fc9 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts @@ -19,6 +19,7 @@ import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'; import type { ContentGenerator } from '@google/gemini-cli-core'; import type { HonoSSEStream } from './types.js'; +import { AIProvider } from './types.js'; import type { GenerateContentParameters, GenerateContentResponse, @@ -177,79 +178,89 @@ export class VercelAIContentGenerator implements ContentGenerator { * Register providers based on config */ private registerProviders(config: VercelAIConfig): void { - if (config.apiKeys?.anthropic) { + const providers = config.providers || {}; + + const anthropicConfig = providers[AIProvider.ANTHROPIC]; + if (anthropicConfig?.apiKey) { this.providerRegistry.set( - 'anthropic', - createAnthropic({ apiKey: config.apiKeys.anthropic }), + AIProvider.ANTHROPIC, + createAnthropic({ apiKey: anthropicConfig.apiKey }), ); } - if (config.apiKeys?.openai) { + const openaiConfig = providers[AIProvider.OPENAI]; + if (openaiConfig?.apiKey) { this.providerRegistry.set( - 'openai', + AIProvider.OPENAI, createOpenAI({ - apiKey: config.apiKeys.openai, - compatibility: 'strict', // Enable streaming token usage + apiKey: openaiConfig.apiKey, + compatibility: 'strict', }), ); } - if (config.apiKeys?.google) { + const googleConfig = providers[AIProvider.GOOGLE]; + if (googleConfig?.apiKey) { this.providerRegistry.set( - 'google', - createGoogleGenerativeAI({ apiKey: config.apiKeys.google }), + AIProvider.GOOGLE, + createGoogleGenerativeAI({ apiKey: googleConfig.apiKey }), ); } - if (config.apiKeys?.openrouter) { + const openrouterConfig = providers[AIProvider.OPENROUTER]; + if (openrouterConfig?.apiKey) { this.providerRegistry.set( - 'openrouter', - createOpenRouter({ apiKey: config.apiKeys.openrouter }), + AIProvider.OPENROUTER, + createOpenRouter({ apiKey: openrouterConfig.apiKey }), ); } - if (config.apiKeys?.azure && config.azureResourceName) { + const azureConfig = providers[AIProvider.AZURE]; + if (azureConfig?.apiKey && azureConfig.resourceName) { this.providerRegistry.set( - 'azure', + AIProvider.AZURE, createAzure({ - resourceName: config.azureResourceName, - apiKey: config.apiKeys.azure, + resourceName: azureConfig.resourceName, + apiKey: azureConfig.apiKey, }), ); } - if (config.lmstudioBaseUrl !== undefined) { + const lmstudioConfig = providers[AIProvider.LMSTUDIO]; + if (lmstudioConfig !== undefined) { this.providerRegistry.set( - 'lmstudio', + AIProvider.LMSTUDIO, createOpenAICompatible({ name: 'lmstudio', - baseURL: config.lmstudioBaseUrl || 'http://localhost:1234/v1', + baseURL: lmstudioConfig.baseUrl || 'http://localhost:1234/v1', }), ); } - if (config.ollamaBaseUrl !== undefined) { + const ollamaConfig = providers[AIProvider.OLLAMA]; + if (ollamaConfig !== undefined) { this.providerRegistry.set( - 'ollama', + AIProvider.OLLAMA, createOpenAICompatible({ name: 'ollama', - baseURL: config.ollamaBaseUrl || 'http://localhost:11434/v1', + baseURL: ollamaConfig.baseUrl || 'http://localhost:11434/v1', }), ); } + const bedrockConfig = providers[AIProvider.BEDROCK]; if ( - config.awsAccessKeyId && - config.awsSecretAccessKey && - config.awsRegion + bedrockConfig?.accessKeyId && + bedrockConfig.secretAccessKey && + bedrockConfig.region ) { this.providerRegistry.set( - 'bedrock', + AIProvider.BEDROCK, createAmazonBedrock({ - region: config.awsRegion, - accessKeyId: config.awsAccessKeyId, - secretAccessKey: config.awsSecretAccessKey, - sessionToken: config.awsSessionToken, + region: bedrockConfig.region, + accessKeyId: bedrockConfig.accessKeyId, + secretAccessKey: bedrockConfig.secretAccessKey, + sessionToken: bedrockConfig.sessionToken, }), ); } @@ -288,10 +299,14 @@ export class VercelAIContentGenerator implements ContentGenerator { throw new Error( `Provider "${provider}" not configured. ` + `Available providers: ${available || 'none'}. ` + - `Add API key in config.apiKeys.${provider}`, + `Configure it in config.providers.${provider}`, ); } return providerInstance; } } + +// Re-export types for consumers +export { AIProvider }; +export type { VercelAIConfig, ProviderConfig, HonoSSEStream } from './types.js'; diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts index 48fe056..08affa7 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts @@ -216,24 +216,40 @@ export interface HonoSSEStream { write(data: string): Promise; } +/** + * Supported AI providers + */ +export enum AIProvider { + ANTHROPIC = 'anthropic', + OPENAI = 'openai', + GOOGLE = 'google', + OPENROUTER = 'openrouter', + AZURE = 'azure', + OLLAMA = 'ollama', + LMSTUDIO = 'lmstudio', + BEDROCK = 'bedrock', +} + +/** + * Provider-specific configuration + */ +export interface ProviderConfig { + apiKey?: string; + baseUrl?: string; + // Azure-specific + resourceName?: string; + // AWS Bedrock-specific + region?: string; + accessKeyId?: string; + secretAccessKey?: string; + sessionToken?: string; +} + /** * Configuration for Vercel AI adapter */ export interface VercelAIConfig { model: string; - apiKeys?: { - anthropic?: string; - openai?: string; - google?: string; - openrouter?: string; - azure?: string; - }; - azureResourceName?: string; - ollamaBaseUrl?: string; - lmstudioBaseUrl?: string; - awsRegion?: string; - awsAccessKeyId?: string; - awsSecretAccessKey?: string; - awsSessionToken?: string; + providers?: Partial>; honoStream?: HonoSSEStream; } From a61b37148b88b460801eb4156b5ef6bfacd16d17 Mon Sep 17 00:00:00 2001 From: shivammittal274 Date: Wed, 26 Nov 2025 23:54:15 +0530 Subject: [PATCH 5/7] agent core logic --- packages/agent/src/agent/Agent.prompt.ts | 339 ----------- packages/agent/src/agent/AgentFactory.ts | 142 ----- packages/agent/src/agent/BaseAgent.test.ts | 176 ------ packages/agent/src/agent/BaseAgent.ts | 222 ------- .../src/agent/ClaudeSDKAgent.formatter.ts | 290 --------- packages/agent/src/agent/ClaudeSDKAgent.ts | 420 ------------- .../agent/src/agent/CodexSDKAgent.config.ts | 75 --- .../src/agent/CodexSDKAgent.formatter.ts | 143 ----- packages/agent/src/agent/CodexSDKAgent.ts | 572 ------------------ .../agent/src/agent/ControllerToolsAdapter.ts | 82 --- packages/agent/src/agent/GeminiAgent.ts | 217 +++++++ .../agent/gemini-vercel-sdk-adapter/index.ts | 222 +++---- .../strategies/message.ts | 27 +- .../strategies/response.ts | 45 +- .../strategies/tool.ts | 8 +- .../agent/gemini-vercel-sdk-adapter/types.ts | 54 +- packages/agent/src/agent/index.ts | 4 + packages/agent/src/agent/registry.ts | 28 - packages/agent/src/agent/types.ts | 163 +---- 19 files changed, 354 insertions(+), 2875 deletions(-) delete mode 100644 packages/agent/src/agent/Agent.prompt.ts delete mode 100644 packages/agent/src/agent/AgentFactory.ts delete mode 100644 packages/agent/src/agent/BaseAgent.test.ts delete mode 100644 packages/agent/src/agent/BaseAgent.ts delete mode 100644 packages/agent/src/agent/ClaudeSDKAgent.formatter.ts delete mode 100644 packages/agent/src/agent/ClaudeSDKAgent.ts delete mode 100644 packages/agent/src/agent/CodexSDKAgent.config.ts delete mode 100644 packages/agent/src/agent/CodexSDKAgent.formatter.ts delete mode 100644 packages/agent/src/agent/CodexSDKAgent.ts delete mode 100644 packages/agent/src/agent/ControllerToolsAdapter.ts create mode 100644 packages/agent/src/agent/GeminiAgent.ts create mode 100644 packages/agent/src/agent/index.ts delete mode 100644 packages/agent/src/agent/registry.ts diff --git a/packages/agent/src/agent/Agent.prompt.ts b/packages/agent/src/agent/Agent.prompt.ts deleted file mode 100644 index 895e0f5..0000000 --- a/packages/agent/src/agent/Agent.prompt.ts +++ /dev/null @@ -1,339 +0,0 @@ - -/** - * @license - * Copyright 2025 BrowserOS - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -/** - * Base system prompt - adapted from OpenAI Codex - * Original source: https://github.com/openai/codex/blob/main/codex-rs/core/prompt.md - */ -const SYSTEM_PROMPT = `You are a browser automation agent. You are expected to be precise, safe, and helpful. - -Your capabilities: - -- Receive user prompts and other context provided by the harness. -- Communicate with the user by streaming thinking & responses, and by making & updating plans. -- Execute browser automation tasks using available tools. - -# How you work - -## Personality - -Your default personality and tone is concise, direct, and friendly. You communicate efficiently, always keeping the user clearly informed about ongoing actions without unnecessary detail. You always prioritize actionable guidance, clearly stating assumptions, environment prerequisites, and next steps. Unless explicitly asked, you avoid excessively verbose explanations about your work. - -## Responsiveness - -### Preamble messages - -Before making tool calls, send a brief preamble to the user explaining what you're about to do. When sending preamble messages, follow these principles and examples: - -- **Logically group related actions**: if you're about to run several related actions, describe them together in one preamble rather than sending a separate note for each. -- **Keep it concise**: be no more than 1-2 sentences, focused on immediate, tangible next steps. (8–12 words for quick updates). -- **Build on prior context**: if this is not your first tool call, use the preamble message to connect the dots with what's been done so far and create a sense of momentum and clarity for the user to understand your next actions. -- **Keep your tone light, friendly and curious**: add small touches of personality in preambles feel collaborative and engaging. -- **Exception**: Avoid adding a preamble for every trivial action (e.g., getting a single tab) unless it's part of a larger grouped action. - -**Examples:** - -- "I've explored the tabs; now checking the page content." -- "Next, I'll navigate to the page and extract the data." -- "I'm about to fill the form fields and submit." -- "Ok cool, so I've got the tab IDs. Now checking the page content." -- "Page is loaded. Next up is clicking the target button." -- "Finished extracting text. I will now parse the results." -- "Alright, tab switching worked. Checking how the page structure looks." -- "Spotted a clever login form; now hunting where the submit button is." - -## Planning - -You have access to an \`update_plan\` tool which tracks steps and progress and renders them to the user. Using the tool helps demonstrate that you've understood the task and convey how you're approaching it. Plans can help to make complex, ambiguous, or multi-phase work clearer and more collaborative for the user. A good plan should break the task into meaningful, logically ordered steps that are easy to verify as you go. - -Note that plans are not for padding out simple work with filler steps or stating the obvious. The content of your plan should not involve doing anything that you aren't capable of doing. Do not use plans for simple or single-step queries that you can just do or answer immediately. - -Do not repeat the full contents of the plan after an \`update_plan\` call — the harness already displays it. Instead, summarize the change made and highlight any important context or next step. - -Before performing an action, consider whether or not you have completed the previous step, and make sure to mark it as completed before moving on to the next step. It may be the case that you complete all steps in your plan after a single pass of execution. If this is the case, you can simply mark all the planned steps as completed. Sometimes, you may need to change plans in the middle of a task: call \`update_plan\` with the updated plan and make sure to provide an \`explanation\` of the rationale when doing so. - -Use a plan when: - -- The task is non-trivial and will require multiple actions over a long time horizon. -- There are logical phases or dependencies where sequencing matters. -- The work has ambiguity that benefits from outlining high-level goals. -- You want intermediate checkpoints for feedback and validation. -- When the user asked you to do more than one thing in a single prompt -- The user has asked you to use the plan tool (aka "TODOs") -- You generate additional steps while working, and plan to do them before yielding to the user - -### Examples - -**High-quality plans** - -Example 1: - -1. Navigate to Amazon product page -2. Add item to shopping cart -3. Proceed to checkout -4. Fill shipping and payment info -5. Place order and get confirmation - -Example 2: - -1. Open GitHub repository page -2. Navigate to Issues tab -3. Click "New Issue" button -4. Fill issue title and description -5. Add labels and submit -6. Extract issue number and URL - -Example 3: - -1. Navigate to Google Forms URL -2. Get all form input fields -3. Fill text inputs and dropdowns -4. Select radio/checkbox options -5. Click submit button -6. Wait for confirmation and extract response - -**Low-quality plans** - -Example 1: - -1. Do the task -2. Get the data -3. Return it - -Example 2: - -1. Navigate to page -2. Click stuff -3. Extract things - -Example 3: - -1. Complete automation -2. Check it worked -3. Give results to user - -If you need to write a plan, only write high quality plans, not low quality ones. - -## Task execution - -Please keep going until the query is completely resolved, before ending your turn and yielding back to the user. Only terminate your turn when you are sure that the problem is solved. Autonomously resolve the query to the best of your ability, using the tools available to you, before coming back to the user. Do NOT guess or make up an answer. - -You MUST adhere to the following criteria when solving queries: - -- Fix the problem at the root cause rather than applying surface-level workarounds, when possible. -- Avoid unneeded complexity in your solution. -- Do not attempt to fix unrelated issues. It is not your responsibility to fix them. (You may mention them to the user in your final message though.) -- Keep your approach consistent with the patterns you observe. Changes should be minimal and focused on the task. - -## Ambition vs. precision - -For tasks that have no prior context (i.e. the user is starting something brand new), you should feel free to be ambitious and demonstrate creativity with your implementation. - -If you're working on an existing flow, you should make sure you do exactly what the user asks with surgical precision. Treat the surrounding context with respect, and don't overstep. You should balance being sufficiently ambitious and proactive when completing tasks of this nature. - -You should use judicious initiative to decide on the right level of detail and complexity to deliver based on the user's needs. This means showing good judgment that you're capable of doing the right extras without gold-plating. This might be demonstrated by high-value, creative touches when scope of the task is vague; while being surgical and targeted when scope is tightly specified. - -## Sharing progress updates - -For especially longer tasks that you work on (i.e. requiring many tool calls, or a plan with multiple steps), you should provide progress updates back to the user at reasonable intervals. These updates should be structured as a concise sentence or two (no more than 8-10 words long) recapping progress so far in plain language: this update demonstrates your understanding of what needs to be done, progress so far (i.e. tabs explored, content extracted), and where you're going next. - -Before doing large chunks of work that may incur latency as experienced by the user, you should send a concise message to the user with an update indicating what you're about to do to ensure they know what you're spending time on. - -The messages you send before tool calls should describe what is immediately about to be done next in very concise language. If there was previous work done, this preamble message should also include a note about the work done so far to bring the user along. - -## Presenting your work and final message - -Your final message should read naturally, like an update from a concise teammate. For casual conversation, brainstorming tasks, or quick questions from the user, respond in a friendly, conversational tone. You should ask questions, suggest ideas, and adapt to the user's style. If you've finished a large amount of work, when describing what you've done to the user, you should follow the final answer formatting guidelines to communicate substantive changes. You don't need to add structured formatting for one-word answers, greetings, or purely conversational exchanges. - -You can skip heavy formatting for single, simple actions or confirmations. In these cases, respond in plain sentences with any relevant next step or quick option. Reserve multi-section structured responses for results that need grouping or explanation. - -If there's something that you think you could help with as a logical next step, concisely ask the user if they want you to do so. Good examples of this are extracting additional data, navigating to related pages, or automating the next logical step. If there's something that you couldn't do but that the user might want to do, include those instructions succinctly. - -Brevity is very important as a default. You should be very concise (i.e. no more than 10 lines), but can relax this requirement for tasks where additional detail and comprehensiveness is important for the user's understanding. - -### Final answer structure and style guidelines - -You are producing plain text that will later be styled. Follow these rules exactly. Formatting should make results easy to scan, but not feel mechanical. Use judgment to decide how much structure adds value. - -**Section Headers** - -- Use only when they improve clarity — they are not mandatory for every answer. -- Choose descriptive names that fit the content -- Keep headers short (1–3 words) and in \`**Title Case**\`. Always start headers with \`**\` and end with \`**\` -- Leave no blank line before the first bullet under a header. -- Section headers should only be used where they genuinely improve scanability; avoid fragmenting the answer. - -**Bullets** - -- Use \`-\` followed by a space for every bullet. -- Merge related points when possible; avoid a bullet for every trivial detail. -- Keep bullets to one line unless breaking for clarity is unavoidable. -- Group into short lists (4–6 bullets) ordered by importance. -- Use consistent keyword phrasing and formatting across sections. - -**Monospace** - -- Wrap all tool names, URLs, and identifiers in backticks (\`\`...\`\`). -- Apply to inline examples and to bullet keywords if the keyword itself is a literal tool/URL. -- Never mix monospace and bold markers; choose one based on whether it's a keyword (\`**\`) or inline reference (\`\`). - -**Structure** - -- Place related bullets together; don't mix unrelated concepts in the same section. -- Order sections from general → specific → supporting info. -- For subsections, introduce with a bolded keyword bullet, then list items under it. -- Match structure to complexity: - - Multi-part or detailed results → use clear headers and grouped bullets. - - Simple results → minimal headers, possibly just a short list or paragraph. - -**Tone** - -- Keep the voice collaborative and natural, like a partner handing off work. -- Be concise and factual — no filler or conversational commentary and avoid unnecessary repetition -- Use present tense and active voice (e.g., "Extracts data" not "This will extract data"). -- Keep descriptions self-contained; don't refer to "above" or "below". -- Use parallel structure in lists for consistency. - -**Don't** - -- Don't use literal words "bold" or "monospace" in the content. -- Don't nest bullets or create deep hierarchies. -- Don't output ANSI escape codes directly — the renderer applies them. -- Don't cram unrelated keywords into a single bullet; split for clarity. -- Don't let keyword lists run long — wrap or reformat for scanability. - -Generally, ensure your final answers adapt their shape and depth to the request. For tasks with a simple implementation, lead with the outcome and supplement only with what's needed for clarity. Larger tasks can be presented as a logical walkthrough of your approach, grouping related steps, explaining rationale where it adds value, and highlighting next actions. Your answers should provide the right level of detail while being easily scannable. - -For casual greetings, acknowledgements, or other one-off conversational messages that are not delivering substantive information or structured results, respond naturally without section headers or bullet formatting. - -## \`update_plan\` - -A tool named \`update_plan\` is available to you. You can use it to keep an up‑to‑date, step‑by‑step plan for the task. - -To create a new plan, call \`update_plan\` with a short list of 1‑sentence steps (no more than 5-7 words each) with a \`status\` for each step (\`pending\`, \`in_progress\`, or \`completed\`). - -When steps have been completed, use \`update_plan\` to mark each finished step as \`completed\` and the next step you are working on as \`in_progress\`. There should always be exactly one \`in_progress\` step until everything is done. You can mark multiple items as complete in a single \`update_plan\` call. - -If all steps are complete, ensure you call \`update_plan\` to mark all steps as \`completed\`.`; - -/** - * BrowserOS-specific tool guidance and workflows - */ -const BROWSEROS_PROMPT = ` -# BrowserOS Tools - -You have access to specialized browser automation tools from the BrowserOS MCP server. - -## Core Principles - -1. **Tab Context Required**: All browser interactions need a valid tab ID. Always identify the target tab first. -2. **Use the Right Tool**: Choose the most efficient tool. Avoid over-engineering simple operations. -3. **Extract, Don't Execute**: Prefer built-in extraction tools over JavaScript execution. - -## Standard Workflow - -Before interacting with any page: -1. Identify target tab via browser_list_tabs or browser_get_active_tab -2. Switch to correct tab if needed via browser_switch_tab -3. Perform action using the tab's ID - -## Tool Selection Guidelines - -### Content Extraction (Priority Order) - -**Text content and data:** -- PREFER: browser_get_page_content(tabId, type) - - type: "text" for plain text - - type: "text-with-links" when URLs needed - - context: "visible" (viewport) or "full" (entire page) - - includeSections: ["main", "article"] to target specific parts - -**Visual context:** -- USE: browser_get_screenshot(tabId) - Only when visual layout matters - - Shows bounding boxes with nodeIds for interactive elements - - Not efficient for text extraction - -**Complex operations:** -- LAST RESORT: browser_execute_javascript(tabId, code) - - Only when built-in tools can't accomplish task - - Use for DOM manipulation or browser API access - -### Tab Management - -- browser_list_tabs - Get all tabs with IDs and URLs -- browser_get_active_tab - Get currently active tab -- browser_switch_tab(tabId) - Switch focus to tab -- browser_open_tab(url, active?) - Open new tab -- browser_close_tab(tabId) - Close tab - -### Navigation - -- browser_navigate(url, tabId?) - Navigate to URL -- browser_get_load_status(tabId) - Check if page loaded - -### Page Interaction - -**Discovery:** -- browser_get_interactive_elements(tabId, simplified?) - Get clickable/typeable elements with nodeIds - - Always call before clicking/typing to get valid nodeIds - -**Actions:** -- browser_click_element(tabId, nodeId) -- browser_type_text(tabId, nodeId, text) -- browser_clear_input(tabId, nodeId) -- browser_send_keys(tabId, key) - Enter, Tab, Escape, Arrow keys, etc. - -**Coordinate-Based:** -- browser_click_coordinates(tabId, x, y) -- browser_type_at_coordinates(tabId, x, y, text) - -### Scrolling - -- browser_scroll_down(tabId) - Scroll down one viewport -- browser_scroll_up(tabId) - Scroll up one viewport -- browser_scroll_to_element(tabId, nodeId) - Scroll element into view - -### Advanced Features - -- browser_get_bookmarks(folderId?) -- browser_create_bookmark(title, url, parentId?) -- browser_remove_bookmark(bookmarkId) -- browser_search_history(query, maxResults?) -- browser_get_recent_history(count?) - -## Best Practices - -- **Minimize Screenshots**: Only when visual context is essential. Prefer browser_get_page_content for data. -- **Avoid Unnecessary JavaScript**: Built-in tools are faster and more reliable. -- **Get Elements First**: Call browser_get_interactive_elements before clicking/typing for valid nodeIds. -- **Wait for Loading**: Verify page loaded after navigation before extracting/interacting. -- **Use Context Options**: Specify "visible" or "full" context when extracting. - -## Common Patterns - -**Extract article:** -\`\`\` -browser_get_page_content(tabId, "text") -\`\`\` - -**Get page links:** -\`\`\` -browser_get_page_content(tabId, "text-with-links") -\`\`\` - -**Fill form:** -\`\`\` -1. browser_get_interactive_elements(tabId) -2. browser_type_text(tabId, inputNodeId, "text") -3. browser_click_element(tabId, submitButtonNodeId) -\`\`\` - -Focus on efficiency. Use the most appropriate tool for each task. When in doubt, prefer simpler tools over complex ones.`; - -/** - * Combined system prompt for browser automation agent - */ -export const AGENT_SYSTEM_PROMPT = SYSTEM_PROMPT + BROWSEROS_PROMPT; diff --git a/packages/agent/src/agent/AgentFactory.ts b/packages/agent/src/agent/AgentFactory.ts deleted file mode 100644 index 08de2d8..0000000 --- a/packages/agent/src/agent/AgentFactory.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import type {ControllerBridge} from '@browseros/controller-server'; - -import type {BaseAgent} from './BaseAgent.js'; -import type {AgentConfig} from './types.js'; - -/** - * Agent constructor signature - * All agents must extend BaseAgent - */ -export type AgentConstructor = new ( - config: AgentConfig, - controllerBridge: ControllerBridge, -) => BaseAgent; - -/** - * Agent registration entry - */ -interface AgentRegistration { - name: string; - constructor: AgentConstructor; - description?: string; -} - -/** - * Agent Factory with Registry Pattern - * - * Allows dynamic agent registration and creation without hardcoded types. - * New agents can be registered at runtime. - * - * @example - * ```typescript - * // Register agents - * AgentFactory.register('codex-sdk', CodexSDKAgent, 'Codex SDK agent') - * AgentFactory.register('claude-sdk', ClaudeSDKAgent, 'Claude SDK agent') - * - * // Create agent dynamically - * const agent = AgentFactory.create('codex-sdk', config, bridge) - * ``` - */ -export class AgentFactory { - private static registry = new Map(); - - /** - * Register an agent type - * - * @param type - Agent type identifier (e.g., 'codex-sdk', 'claude-sdk') - * @param constructor - Agent class constructor - * @param description - Optional description - */ - static register( - type: string, - constructor: AgentConstructor, - description?: string, - ): void { - if (this.registry.has(type)) { - throw new Error(`Agent type '${type}' is already registered`); - } - - this.registry.set(type, { - name: type, - constructor, - description, - }); - } - - /** - * Create an agent instance - * - * @param type - Agent type identifier - * @param config - Agent configuration - * @param controllerBridge - Shared controller bridge - * @returns BaseAgent instance - * @throws Error if agent type is not registered - */ - static create( - type: string, - config: AgentConfig, - controllerBridge: ControllerBridge, - ): BaseAgent { - const registration = this.registry.get(type); - - if (!registration) { - const availableTypes = Array.from(this.registry.keys()).join(', '); - throw new Error( - `Agent type '${type}' is not registered. Available types: ${availableTypes}`, - ); - } - - return new registration.constructor(config, controllerBridge); - } - - /** - * Check if an agent type is registered - * - * @param type - Agent type identifier - * @returns true if registered - */ - static has(type: string): boolean { - return this.registry.has(type); - } - - /** - * Get all registered agent types - * - * @returns Array of registered agent type identifiers - */ - static getAvailableTypes(): string[] { - return Array.from(this.registry.keys()); - } - - /** - * Get registration info for an agent type - * - * @param type - Agent type identifier - * @returns Registration info or undefined - */ - static getRegistration(type: string): AgentRegistration | undefined { - return this.registry.get(type); - } - - /** - * Unregister an agent type (useful for testing) - * - * @param type - Agent type identifier - * @returns true if unregistered, false if not found - */ - static unregister(type: string): boolean { - return this.registry.delete(type); - } - - /** - * Clear all registrations (useful for testing) - */ - static clear(): void { - this.registry.clear(); - } -} diff --git a/packages/agent/src/agent/BaseAgent.test.ts b/packages/agent/src/agent/BaseAgent.test.ts deleted file mode 100644 index 6229c94..0000000 --- a/packages/agent/src/agent/BaseAgent.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {describe, it, expect, beforeEach} from 'bun:test'; - -import type {FormattedEvent} from '../utils/EventFormatter.js'; - -import {BaseAgent, DEFAULT_CONFIG} from './BaseAgent.js'; -import type {AgentConfig} from './types.js'; - -// Concrete test implementation of BaseAgent -class TestAgent extends BaseAgent { - constructor(config: AgentConfig, agentDefaults?: Partial) { - super('test-agent', config, agentDefaults); - } - - async *execute(message: string): AsyncGenerator { - // Minimal implementation for testing - yield {type: 'test', content: message, metadata: {}} as any; - } - - async destroy(): Promise { - this.markDestroyed(); - } -} - -describe('BaseAgent-unit-test', () => { - // Unit Test 1 - Constructor and config merging with defaults - it('tests that configs merge correctly with defaults', () => { - const userConfig: AgentConfig = { - resourcesDir: '/test/resources', - executionDir: '/test/execution', - apiKey: 'test-key', - maxTurns: 50, - // systemPrompt not provided, should use default - }; - - const agentDefaults = { - systemPrompt: 'Agent-specific prompt', - maxTurns: 75, - maxThinkingTokens: 5000, - }; - - const agent = new TestAgent(userConfig, agentDefaults); - - // Verify config merging priority: user > agent defaults > base defaults - expect(agent['config'].resourcesDir).toBe('/test/resources'); - expect(agent['config'].apiKey).toBe('test-key'); - expect(agent['config'].maxTurns).toBe(50); // User overrides agent default - expect(agent['config'].systemPrompt).toBe('Agent-specific prompt'); // Agent default used - expect(agent['config'].maxThinkingTokens).toBe(5000); // Agent default used - }); - - // Unit Test 2 - Metadata initialization and state tracking - it('tests that metadata initializes with correct state', () => { - const config: AgentConfig = { - resourcesDir: '/test/resources', - executionDir: '/test/execution', - apiKey: 'test-key', - }; - - const agent = new TestAgent(config); - const metadata = agent.getMetadata(); - - // Verify initial metadata state - expect(metadata.type).toBe('test-agent'); - expect(metadata.state).toBe('idle'); - expect(metadata.turns).toBe(0); - expect(metadata.toolsExecuted).toBe(0); - expect(metadata.totalDuration).toBe(0); - expect(metadata.lastEventTime).toBeGreaterThan(0); - }); - - // Unit Test 3 - Execution state transitions - it('tests that execution state tracks correctly', () => { - const config: AgentConfig = { - resourcesDir: '/test/resources', - executionDir: '/test/execution', - apiKey: 'test-key', - }; - - const agent = new TestAgent(config); - - // Initial state - expect(agent['metadata'].state).toBe('idle'); - - // Start execution - agent['startExecution'](); - expect(agent['metadata'].state).toBe('executing'); - expect(agent['executionStartTime']).toBeGreaterThan(0); - - const startTime = agent['executionStartTime']; - - // Complete execution - agent['completeExecution'](); - expect(agent['metadata'].state).toBe('idle'); - expect(agent['metadata'].totalDuration).toBeGreaterThanOrEqual(0); - }); - - // Unit Test 4 - Metadata update methods - it('tests that metadata updates through helper methods', () => { - const config: AgentConfig = { - resourcesDir: '/test/resources', - executionDir: '/test/execution', - apiKey: 'test-key', - }; - - const agent = new TestAgent(config); - const initialEventTime = agent['metadata'].lastEventTime; - - // Update event time - agent['updateEventTime'](); - expect(agent['metadata'].lastEventTime).toBeGreaterThanOrEqual( - initialEventTime, - ); - - // Increment tools executed - agent['updateToolsExecuted'](3); - expect(agent['metadata'].toolsExecuted).toBe(3); - - agent['updateToolsExecuted'](); // Default increment by 1 - expect(agent['metadata'].toolsExecuted).toBe(4); - - // Update turns - agent['updateTurns'](10); - expect(agent['metadata'].turns).toBe(10); - }); - - // Unit Test 5 - Error state handling - it('tests that error state handles correctly', () => { - const config: AgentConfig = { - resourcesDir: '/test/resources', - executionDir: '/test/execution', - apiKey: 'test-key', - }; - - const agent = new TestAgent(config); - - // Mark error with Error object - const error = new Error('Test error'); - agent['errorExecution'](error); - - expect(agent['metadata'].state).toBe('error'); - expect(agent['metadata'].error).toBe('Test error'); - - // Mark error with string - const agent2 = new TestAgent(config); - agent2['errorExecution']('String error'); - - expect(agent2['metadata'].state).toBe('error'); - expect(agent2['metadata'].error).toBe('String error'); - }); - - // Unit Test 6 - Destroyed state tracking - it('tests that destroyed state tracks correctly', async () => { - const config: AgentConfig = { - resourcesDir: '/test/resources', - executionDir: '/test/execution', - apiKey: 'test-key', - }; - - const agent = new TestAgent(config); - - // Initially not destroyed - expect(agent['isDestroyed']()).toBe(false); - - // Destroy agent - await agent.destroy(); - - // Should be marked as destroyed - expect(agent['isDestroyed']()).toBe(true); - expect(agent['metadata'].state).toBe('destroyed'); - }); -}); diff --git a/packages/agent/src/agent/BaseAgent.ts b/packages/agent/src/agent/BaseAgent.ts deleted file mode 100644 index 1ab23c7..0000000 --- a/packages/agent/src/agent/BaseAgent.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {logger} from '@browseros/common'; - -import type {AgentConfig, AgentMetadata, FormattedEvent} from './types.js'; - -/** - * Generic default system prompt for agents - * - * Minimal prompt - agents should override with their own specific prompts - */ -export const DEFAULT_SYSTEM_PROMPT = `You are a browser automation agent.`; - -/** - * Generic default configuration values - * - * Agents can override these with their own defaults - */ -export const DEFAULT_CONFIG = { - maxTurns: 100, - maxThinkingTokens: 10000, - systemPrompt: DEFAULT_SYSTEM_PROMPT, - mcpServers: {}, -}; - -/** - * BaseAgent - Abstract base class for all agent implementations - * - * Provides: - * - Common configuration handling with defaults - * - Metadata management - * - Logging helpers - * - Abstract methods that concrete agents must implement - * - * Subclasses can override defaults by passing them to the constructor. - * - * Usage: - * export class MyAgent extends BaseAgent { - * constructor(config: AgentConfig) { - * super('my-agent', config, { - * systemPrompt: 'My custom prompt', - * mcpServers: { ... }, - * maxTurns: 50 - * }) - * } - * async *execute(message: string): AsyncGenerator { - * // Implementation - * } - * async destroy(): Promise { - * // Cleanup - * } - * } - */ -export abstract class BaseAgent { - protected config: Required; - protected metadata: AgentMetadata; - protected executionStartTime = 0; - protected initialized = false; - - constructor( - agentType: string, - config: AgentConfig, - agentDefaults?: Partial, - ) { - // Merge config with agent-specific defaults, then with base defaults - this.config = { - resourcesDir: config.resourcesDir, - executionDir: config.executionDir, - mcpServerPort: config.mcpServerPort ?? agentDefaults?.mcpServerPort, - apiKey: config.apiKey ?? agentDefaults?.apiKey, - baseUrl: config.baseUrl, - modelName: config.modelName, - maxTurns: - config.maxTurns ?? agentDefaults?.maxTurns ?? DEFAULT_CONFIG.maxTurns, - maxThinkingTokens: - config.maxThinkingTokens ?? - agentDefaults?.maxThinkingTokens ?? - DEFAULT_CONFIG.maxThinkingTokens, - systemPrompt: - config.systemPrompt ?? - agentDefaults?.systemPrompt ?? - DEFAULT_CONFIG.systemPrompt, - mcpServers: - config.mcpServers ?? - agentDefaults?.mcpServers ?? - DEFAULT_CONFIG.mcpServers, - } as Required; - - // Initialize metadata - this.metadata = { - type: agentType, - turns: 0, - totalDuration: 0, - lastEventTime: Date.now(), - toolsExecuted: 0, - state: 'idle', - }; - - logger.debug(`🤖 ${agentType} agent created`, { - agentType, - resourcesDir: this.config.resourcesDir, - modelName: this.config.modelName, - baseUrl: this.config.baseUrl, - maxTurns: this.config.maxTurns, - maxThinkingTokens: this.config.maxThinkingTokens, - usingDefaultMcp: !config.mcpServers, - usingDefaultPrompt: !config.systemPrompt, - }); - } - - /** - * Async initialization for agents that need it - * Subclasses can override for async setup (e.g., fetching config) - */ - async init(): Promise { - this.initialized = true; - } - - /** - * Execute a task and stream events - * Must be implemented by concrete agent classes - */ - // FIXME: make it handle init if not initialized - abstract execute(message: string): AsyncGenerator; - - /** - * Cleanup agent resources - * Must be implemented by concrete agent classes - */ - abstract destroy(): Promise; - - /** - * Abort current execution - * Triggers the abort signal to stop the current task - * Must be implemented by concrete agent classes - */ - abstract abort(): void; - - /** - * Check if agent is currently executing - * Must be implemented by concrete agent classes - */ - abstract isExecuting(): boolean; - - /** - * Get current agent metadata - */ - getMetadata(): AgentMetadata { - return {...this.metadata}; - } - - /** - * Helper: Start execution tracking - */ - protected startExecution(): void { - this.metadata.state = 'executing'; - this.executionStartTime = Date.now(); - } - - /** - * Helper: Complete execution tracking - */ - protected completeExecution(): void { - this.metadata.state = 'idle'; - this.metadata.totalDuration += Date.now() - this.executionStartTime; - } - - /** - * Helper: Mark execution error - */ - protected errorExecution(error: Error | string): void { - this.metadata.state = 'error'; - this.metadata.error = error instanceof Error ? error.message : error; - } - - /** - * Helper: Update last event time - */ - protected updateEventTime(): void { - this.metadata.lastEventTime = Date.now(); - } - - /** - * Helper: Increment tool execution count - */ - protected updateToolsExecuted(count = 1): void { - this.metadata.toolsExecuted += count; - } - - /** - * Helper: Update turn count - */ - protected updateTurns(turns: number): void { - this.metadata.turns = turns; - } - - /** - * Helper: Check if agent is destroyed - */ - protected isDestroyed(): boolean { - return this.metadata.state === 'destroyed'; - } - - /** - * Helper: Mark agent as destroyed - */ - protected markDestroyed(): void { - this.metadata.state = 'destroyed'; - } - - /** - * Helper: Ensure agent is initialized - */ - protected ensureInitialized(): void { - if (!this.initialized) { - throw new Error('Agent not initialized. Call init() before execute()'); - } - } -} diff --git a/packages/agent/src/agent/ClaudeSDKAgent.formatter.ts b/packages/agent/src/agent/ClaudeSDKAgent.formatter.ts deleted file mode 100644 index 2c64f0d..0000000 --- a/packages/agent/src/agent/ClaudeSDKAgent.formatter.ts +++ /dev/null @@ -1,290 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {FormattedEvent} from './types.js'; - -/** - * Claude SDK Event Formatter - * - * Handles Claude-specific event structure: - * - system: Initialization and MCP notifications - * - assistant: Messages, tool calls, thinking - * - user: Tool results - * - result: Final completion/error events - */ -export class ClaudeEventFormatter { - /** - * Format Claude SDK event into common FormattedEvent - * - * @param event - Raw Claude event - * @returns FormattedEvent or null if event should not be displayed - */ - static format(event: any): FormattedEvent | null { - const eventType = event.type; - const subtype = (event as any).subtype; - - if (eventType === 'system') { - if (subtype === 'init') { - return this.formatInit(event); - } - if (subtype === 'mcp_server_notification') { - return this.formatMcpNotification(event); - } - return new FormattedEvent('init', 'System initialized'); - } - - if (eventType === 'assistant') { - return this.formatAssistant(event); - } - - if (eventType === 'user') { - return this.formatToolResults(event); - } - - if (eventType === 'result') { - return this.formatResult(event); - } - - return null; - } - - /** - * Format system initialization event - */ - private static formatInit(event: any): FormattedEvent { - const mcpServers = event.mcp_servers || []; - const toolCount = event.tools?.length || 0; - - if (mcpServers.length > 0) { - const serverNames = mcpServers.map((s: any) => s.name).join(', '); - return new FormattedEvent( - 'init', - `Initializing agent with ${toolCount} tools and MCP servers: ${serverNames}`, - ); - } - - return new FormattedEvent( - 'init', - `Initializing agent with ${toolCount} tools`, - ); - } - - /** - * Format MCP server notifications - */ - private static formatMcpNotification(event: any): FormattedEvent { - return new FormattedEvent( - 'init', - `MCP notification: ${JSON.stringify(event.params)}`, - ); - } - - /** - * Format assistant messages (text, tool calls, thinking) - */ - private static formatAssistant(event: any): FormattedEvent | null { - const message = event.message; - if (!message?.content || !Array.isArray(message.content)) { - return null; - } - - const toolUses = message.content.filter((c: any) => c.type === 'tool_use'); - if (toolUses.length > 0) { - return this.formatToolUse(toolUses); - } - - const textContent = message.content.find((c: any) => c.type === 'text'); - if (textContent) { - return new FormattedEvent('response', textContent.text); - } - - const thinkingContent = message.content.find( - (c: any) => c.type === 'thinking', - ); - if (thinkingContent) { - const text = thinkingContent.thinking || ''; - const truncated = - text.length > 100 ? text.substring(0, 100) + '...' : text; - return new FormattedEvent('thinking', `💭 ${truncated}`); - } - - return null; - } - - /** - * Format tool use events - */ - private static formatToolUse(toolUses: any[]): FormattedEvent { - if (toolUses.length === 1) { - const tool = toolUses[0]; - const toolName = this.cleanToolName(tool.name); - const args = this.formatToolArgs(tool.input); - const argsText = args ? `\n Args: ${args}` : ''; - return new FormattedEvent('tool_use', `🔧 ${toolName}${argsText}`); - } - - const toolNames = toolUses - .map((t: any) => this.cleanToolName(t.name)) - .join(', '); - return new FormattedEvent('tool_use', `🔧 ${toolNames}`); - } - - /** - * Format tool result events - */ - private static formatToolResults(event: any): FormattedEvent | null { - const message = event.message; - if (!message?.content || !Array.isArray(message.content)) { - return null; - } - - const toolResults = message.content.filter( - (c: any) => c.type === 'tool_result', - ); - if (toolResults.length === 0) { - return null; - } - - for (const result of toolResults) { - if (result.is_error || result.error) { - const errorMsg = - result.error || result.content?.[0]?.text || 'Unknown error'; - return new FormattedEvent('tool_result', `❌ Error: ${errorMsg}`); - } - } - - const resultTexts = toolResults - .map((r: any) => this.extractTextFromContent(r.content)) - .filter((t: string) => t.length > 0); - - if (resultTexts.length === 0) { - return new FormattedEvent('tool_result', '✓ Tool executed'); - } - - const combinedText = resultTexts.join('\n'); - const truncated = - combinedText.length > 200 - ? combinedText.substring(0, 200) + '...' - : combinedText; - - const hasImages = toolResults.some((r: any) => - this.hasImageContent(r.content), - ); - const imageIndicator = hasImages ? ' 📷' : ''; - - return new FormattedEvent('tool_result', `✓ ${truncated}${imageIndicator}`); - } - - /** - * Format result events (completion/error) - */ - private static formatResult(event: any): FormattedEvent { - const subtype = event.subtype; - const metadata = { - turnCount: event.turn_count || 0, - isError: subtype === 'error', - duration: event.duration_ms || 0, - }; - - if (subtype === 'completion') { - const usageInfo = event.usage - ? ` (${event.usage.input_tokens}/${event.usage.output_tokens} tokens)` - : ''; - return new FormattedEvent( - 'completion', - `✅ Completed${usageInfo}`, - metadata, - ); - } - - if (subtype === 'error') { - const errorMsg = event.error?.message || 'Unknown error'; - return new FormattedEvent('error', `❌ Error: ${errorMsg}`, metadata); - } - - const errorMsg = event.error?.message || event.message || 'Task stopped'; - return new FormattedEvent('completion', `⏹️ ${errorMsg}`, metadata); - } - - /** - * Create heartbeat/processing event - */ - static createProcessingEvent(): FormattedEvent { - return new FormattedEvent('thinking', '⏳ Processing...'); - } - - /** - * Clean tool name by removing prefixes - */ - private static cleanToolName(name: string): string { - return name - .replace(/^mcp__[^_]+__/, '') - .replace(/^browseros-controller__/, '') - .replace(/_/g, ' '); - } - - /** - * Format tool arguments into readable string - */ - private static formatToolArgs(input: any): string { - if (!input || typeof input !== 'object') { - return ''; - } - - const keys = Object.keys(input); - if (keys.length === 0) { - return ''; - } - - if (keys.length === 1 && keys[0] === 'url') { - return input.url; - } - - if (keys.length === 1 && (keys[0] === 'function' || keys[0] === 'script')) { - const code = input[keys[0]]; - if (typeof code === 'string') { - return code.length > 50 ? code.substring(0, 50) + '...' : code; - } - } - - const argPairs = keys.map(key => { - const value = input[key]; - if (typeof value === 'string') { - return `${key}="${value.length > 30 ? value.substring(0, 30) + '...' : value}"`; - } - return `${key}=${JSON.stringify(value)}`; - }); - - return argPairs.join(', '); - } - - /** - * Extract text content from tool result content - */ - private static extractTextFromContent(content: any): string { - if (typeof content === 'string') { - return content; - } - - if (Array.isArray(content)) { - const textBlocks = content - .filter((c: any) => c.type === 'text') - .map((c: any) => c.text); - return textBlocks.join('\n'); - } - - return ''; - } - - /** - * Check if content contains images - */ - private static hasImageContent(content: any): boolean { - if (Array.isArray(content)) { - return content.some((c: any) => c.type === 'image'); - } - return false; - } -} diff --git a/packages/agent/src/agent/ClaudeSDKAgent.ts b/packages/agent/src/agent/ClaudeSDKAgent.ts deleted file mode 100644 index de723a9..0000000 --- a/packages/agent/src/agent/ClaudeSDKAgent.ts +++ /dev/null @@ -1,420 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {query} from '@anthropic-ai/claude-agent-sdk'; -import { - logger, - fetchBrowserOSConfig, - type BrowserOSConfig, - type Provider, -} from '@browseros/common'; -import type { - ControllerBridge} from '@browseros/controller-server'; -import { - ControllerContext, -} from '@browseros/controller-server'; -import type {ToolDefinition} from '@browseros/tools'; -import {allControllerTools} from '@browseros/tools/controller-based'; - -import {AGENT_SYSTEM_PROMPT} from './Agent.prompt.js'; -import {BaseAgent} from './BaseAgent.js'; -import {ClaudeEventFormatter} from './ClaudeSDKAgent.formatter.js'; -import {createControllerMcpServer} from './ControllerToolsAdapter.js'; -import { type AgentConfig} from './types.js'; -import type {FormattedEvent} from './types.js'; - -/** - * Claude SDK specific default configuration - */ -const CLAUDE_SDK_DEFAULTS = { - maxTurns: 100, - maxThinkingTokens: 10000, -}; - -/** - * Claude SDK Agent implementation - * - * Wraps @anthropic-ai/claude-agent-sdk with: - * - In-process SDK MCP server with controller tools - * - Shared ControllerBridge for browseros-controller connection - * - Event formatting via EventFormatter - * - AbortController for cleanup - * - Metadata tracking - * - * Note: Requires external ControllerBridge (provided by main server) - */ -export class ClaudeSDKAgent extends BaseAgent { - private abortController: AbortController | null = null; - private gatewayConfig: BrowserOSConfig | null = null; - private selectedProvider: Provider | null = null; - - constructor(config: AgentConfig, controllerBridge: ControllerBridge) { - logger.info('🔧 Using shared ControllerBridge for controller connection'); - - const controllerContext = new ControllerContext(controllerBridge); - - // Get all controller tools from package and create SDK MCP server - const sdkMcpServer = createControllerMcpServer( - allControllerTools, - controllerContext, - ); - - logger.info( - `✅ Created SDK MCP server with ${allControllerTools.length} controller tools`, - ); - - // Pass Claude SDK specific defaults to BaseAgent (must call super before accessing this) - super('claude-sdk', config, { - systemPrompt: AGENT_SYSTEM_PROMPT, - mcpServers: {'browseros-controller': sdkMcpServer}, - maxTurns: CLAUDE_SDK_DEFAULTS.maxTurns, - maxThinkingTokens: CLAUDE_SDK_DEFAULTS.maxThinkingTokens, - }); - - logger.info('✅ ClaudeSDKAgent initialized with shared ControllerBridge'); - } - - /** - * Initialize agent - fetch config from BrowserOS Config URL if configured - * Falls back to ANTHROPIC_API_KEY env var if config URL not set or fails - */ - override async init(): Promise { - const configUrl = process.env.BROWSEROS_CONFIG_URL; - - if (configUrl) { - logger.info('🌐 Fetching config from BrowserOS Config URL', {configUrl}); - - try { - this.gatewayConfig = await fetchBrowserOSConfig(configUrl); - this.selectedProvider = - this.gatewayConfig.providers.find(p => p.name === 'anthropic') || null; - - if (!this.selectedProvider) { - throw new Error('No anthropic provider found in config'); - } - - this.config.apiKey = this.selectedProvider.apiKey; - if (this.selectedProvider.baseUrl) { - this.config.baseUrl = this.selectedProvider.baseUrl; - } - if (this.selectedProvider.model) { - this.config.modelName = this.selectedProvider.model; - } - - logger.info('✅ Using config from BrowserOS Config URL', { - model: this.config.modelName, - baseUrl: this.config.baseUrl, - }); - - await super.init(); - return; - } catch (error) { - logger.warn( - '⚠️ Failed to fetch from config URL, falling back to ANTHROPIC_API_KEY', - { - error: error instanceof Error ? error.message : String(error), - }, - ); - } - } - - const envApiKey = process.env.ANTHROPIC_API_KEY; - if (envApiKey) { - this.config.apiKey = envApiKey; - logger.info('✅ Using API key from ANTHROPIC_API_KEY env var'); - await super.init(); - return; - } - - throw new Error( - 'No API key found. Set either BROWSEROS_CONFIG_URL or ANTHROPIC_API_KEY', - ); - } - - /** - * Wrapper around iterator.next() that yields heartbeat events while waiting - * @param iterator - The async iterator - * @yields Heartbeat events (FormattedEvent) while waiting, then the final iterator result (IteratorResult) - */ - private async *nextWithHeartbeat( - iterator: AsyncIterator, - ): AsyncGenerator { - const heartbeatInterval = 20000; // 20 seconds - let heartbeatTimer: NodeJS.Timeout | null = null; - let abortHandler: (() => void) | null = null; - - // Call iterator.next() once - this generator wraps a single next() call - const iteratorPromise = iterator.next(); - - // Create abort promise - const abortPromise = new Promise((_, reject) => { - if (this.abortController) { - abortHandler = () => { - reject(new Error('Agent execution aborted by client')); - }; - this.abortController.signal.addEventListener('abort', abortHandler, { - once: true, - }); - } - }); - - try { - // Loop until the iterator promise resolves, yielding heartbeats while waiting - while (true) { - // Check if execution was aborted - if (this.abortController?.signal.aborted) { - logger.info('⚠️ Agent execution aborted during heartbeat wait'); - return; - } - - // Create timeout promise for this iteration - const timeoutPromise = new Promise(resolve => { - heartbeatTimer = setTimeout( - () => resolve({type: 'heartbeat'}), - heartbeatInterval, - ); - }); - - type RaceResult = {type: 'result'; result: any} | {type: 'heartbeat'}; - let race: RaceResult; - - try { - race = await Promise.race([ - iteratorPromise.then(result => ({type: 'result' as const, result})), - timeoutPromise.then(() => ({type: 'heartbeat' as const})), - abortPromise, - ]); - } catch (abortError) { - // Abort was triggered during wait - logger.info( - '⚠️ Agent execution aborted (caught during iterator wait)', - ); - // Cleanup iterator (fire-and-forget to avoid blocking) - if (iterator.return) { - iterator.return(undefined).catch(() => {}); - } - return; - } - - // Clear the timeout if it was set - if (heartbeatTimer) { - clearTimeout(heartbeatTimer); - heartbeatTimer = null; - } - - if (race.type === 'heartbeat') { - // Heartbeat timeout occurred - yield processing event and continue waiting - yield ClaudeEventFormatter.createProcessingEvent(); - // Loop continues - will race the same iteratorPromise (still pending) vs new timeout - } else { - // Iterator result arrived - yield it and exit this generator - yield race.result; - return; - } - } - } finally { - // Clean up heartbeat timer - if (heartbeatTimer) { - clearTimeout(heartbeatTimer); - } - - // Clean up abort listener if it wasn't triggered - if ( - abortHandler && - this.abortController && - !this.abortController.signal.aborted - ) { - this.abortController.signal.removeEventListener('abort', abortHandler); - } - } - } - - /** - * Execute a task using Claude SDK and stream formatted events - * - * @param message - User's natural language request - * @yields FormattedEvent instances - */ - async *execute(message: string): AsyncGenerator { - if (!this.initialized) { - await this.init(); - } - - this.startExecution(); - this.abortController = new AbortController(); - - logger.info('🤖 ClaudeSDKAgent executing', {message}); - - try { - const options: any = { - apiKey: this.config.apiKey, - maxTurns: this.config.maxTurns, - maxThinkingTokens: this.config.maxThinkingTokens, - cwd: this.config.executionDir, - systemPrompt: this.config.systemPrompt, - mcpServers: this.config.mcpServers, - abortController: this.abortController, - }; - - if (this.config.modelName) { - options.model = this.config.modelName; - logger.debug('Using model from config', { - model: this.config.modelName, - }); - } - - if (this.config.baseUrl) { - options.baseUrl = this.config.baseUrl; - logger.debug('Using custom base URL', { - baseUrl: this.config.baseUrl, - }); - } - - // Call Claude SDK - const iterator = query({prompt: message, options})[ - Symbol.asyncIterator - ](); - - // Stream events with heartbeat - while (true) { - // Check if execution was aborted - if (this.abortController?.signal.aborted) { - logger.info('⚠️ Agent execution aborted by client'); - break; - } - - let result: IteratorResult | null = null; - - // Iterate through heartbeat generator to get the actual result - for await (const item of this.nextWithHeartbeat(iterator)) { - if (item && item.done !== undefined) { - // This is the final result - result = item; - } else { - // This is a heartbeat/processing event - yield item; - } - } - - if (!result || result.done) break; - - const event = result.value; - - // Update event time - this.updateEventTime(); - - // Track tool executions (check for assistant message with tool_use content) - if (event.type === 'assistant' && (event as any).message?.content) { - const toolUses = (event as any).message.content.filter( - (c: any) => c.type === 'tool_use', - ); - if (toolUses.length > 0) { - this.updateToolsExecuted(toolUses.length); - } - } - - // Track turn count from result events - if (event.type === 'result') { - const numTurns = (event as any).num_turns; - if (numTurns) { - this.updateTurns(numTurns); - } - - // Log raw result events for debugging - logger.info('📊 Raw result event', { - subtype: (event as any).subtype, - is_error: (event as any).is_error, - num_turns: numTurns, - result: (event as any).result ?? 'N/A', - }); - } - - // Format the event using ClaudeEventFormatter - const formattedEvent = ClaudeEventFormatter.format(event); - - // Yield formatted event if valid - if (formattedEvent) { - logger.debug('📤 ClaudeSDKAgent yielding event', { - type: formattedEvent.type, - }); - yield formattedEvent; - } - } - - // Complete execution tracking - this.completeExecution(); - - logger.info('✅ ClaudeSDKAgent execution complete', { - turns: this.metadata.turns, - toolsExecuted: this.metadata.toolsExecuted, - duration: Date.now() - this.executionStartTime, - }); - } catch (error) { - // Mark execution error - this.errorExecution( - error instanceof Error ? error : new Error(String(error)), - ); - - logger.error('❌ ClaudeSDKAgent execution failed', { - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, - }); - - throw error; - } finally { - // Clear AbortController reference - this.abortController = null; - } - } - - /** - * Abort current execution - * Triggers abort signal to stop the current task gracefully - */ - abort(): void { - if (this.abortController) { - logger.info('🛑 Aborting ClaudeSDKAgent execution'); - this.abortController.abort(); - } else { - logger.warn('⚠️ Cancel not fully supported - no active execution'); - } - } - - /** - * Check if agent is currently executing - */ - isExecuting(): boolean { - return this.metadata.state === 'executing' && this.abortController !== null; - } - - /** - * Cleanup agent resources - * - * Aborts the running SDK query. Does NOT close shared ControllerBridge. - */ - async destroy(): Promise { - if (this.isDestroyed()) { - logger.debug('⚠️ ClaudeSDKAgent already destroyed'); - return; - } - - this.markDestroyed(); - - // Abort the SDK query if it's running - if (this.abortController) { - logger.debug('🛑 Aborting SDK query'); - this.abortController.abort(); - await new Promise(resolve => setTimeout(resolve, 500)); - } - - // DO NOT close ControllerBridge - it's shared and owned by main server - - logger.debug('🗑️ ClaudeSDKAgent destroyed', { - totalDuration: this.metadata.totalDuration, - turns: this.metadata.turns, - toolsExecuted: this.metadata.toolsExecuted, - }); - } -} diff --git a/packages/agent/src/agent/CodexSDKAgent.config.ts b/packages/agent/src/agent/CodexSDKAgent.config.ts deleted file mode 100644 index 2c2da81..0000000 --- a/packages/agent/src/agent/CodexSDKAgent.config.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {writeFileSync} from 'node:fs'; -import {join} from 'node:path'; - -import {logger} from '@browseros/common'; -import {stringify} from 'smol-toml'; - -export interface McpServerConfig { - url: string; - startup_timeout_sec?: number; - tool_timeout_sec?: number; -} - -export interface BrowserOSCodexConfig { - model_name: string; - base_url?: string; - api_key_env: string; - wire_api: 'chat' | 'responses'; - base_instructions_file: string; - mcp_servers: { - [key: string]: McpServerConfig; - }; -} - -export function generateBrowserOSCodexToml( - config: BrowserOSCodexConfig, -): string { - const header = [ - '# BrowserOS Model Provider Configuration', - '# This file configures a custom model provider for Codex', - '', - ].join('\n'); - - const tomlContent = stringify(config); - - return header + tomlContent; -} - -export function writeBrowserOSCodexConfig( - config: BrowserOSCodexConfig, - outputDir: string, -): string { - const tomlContent = generateBrowserOSCodexToml(config); - const tomlPath = join(outputDir, 'browseros_config.toml'); - - writeFileSync(tomlPath, tomlContent, 'utf-8'); - - logger.info('✅ Generated BrowserOS Codex config', { - path: tomlPath, - modelName: config.model_name, - baseUrl: config.base_url, - }); - - return tomlPath; -} - -export function writePromptFile( - promptContent: string, - outputDir: string, -): string { - const promptPath = join(outputDir, 'browseros_prompt.md'); - - writeFileSync(promptPath, promptContent, 'utf-8'); - - logger.info('✅ Generated BrowserOS prompt file', { - path: promptPath, - size: promptContent.length, - }); - - return promptPath; -} diff --git a/packages/agent/src/agent/CodexSDKAgent.formatter.ts b/packages/agent/src/agent/CodexSDKAgent.formatter.ts deleted file mode 100644 index 84f0eb3..0000000 --- a/packages/agent/src/agent/CodexSDKAgent.formatter.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import type {ThreadEvent} from '@browseros/codex-sdk-ts'; -import type {ThreadItem} from '@browseros/codex-sdk-ts'; -import {FormattedEvent} from './types.js'; - -/** - * Codex SDK Event Formatter - * - * Maps Codex events to FormattedEvent types: - * - thread.started -> init - * - turn.started -> thinking - * - item.started/item.completed -> various (thinking, tool_use, tool_result, error) - * - turn.failed -> error - * - error -> error - * - * Note: turn.completed is handled in CodexSDKAgent.execute() to re-emit final agent_message as completion - */ -export class CodexEventFormatter { - /** - * Format Codex SDK event into FormattedEvent - * - * @param event - Raw Codex event - * @returns FormattedEvent or null if event should not be displayed - */ - static format(event: ThreadEvent): FormattedEvent | null { - switch (event.type) { - case 'thread.started': - // return new FormattedEvent('init', `Thread started: ${event.thread_id}`); - // No need to show thread started event to user - return null; - - case 'turn.started': - return new FormattedEvent('thinking', 'Agent processing...'); - - case 'item.started': - case 'item.completed': - return this.formatItem(event.item); - - case 'turn.failed': - return new FormattedEvent( - 'error', - `Turn failed: ${event.error.message}`, - ); - - case 'error': - return new FormattedEvent('error', event.message); - - case 'turn.completed': - return null; - - default: - return null; - } - } - - /** - * Format Codex item based on type - */ - private static formatItem(item: ThreadItem): FormattedEvent | null { - switch (item.type) { - case 'agent_message': - return new FormattedEvent('thinking', item.text); - - case 'reasoning': { - const text = item.text; - if (!text) return null; - const truncated = - text.length > 150 ? text.substring(0, 150) + '...' : text; - return new FormattedEvent('thinking', truncated); - } - - case 'mcp_tool_call': { - const toolName = this.cleanToolName(item.tool); - const status = item.status; - - if (status === 'in_progress') { - return new FormattedEvent('tool_use', `Executing ${toolName}`); - } else if (status === 'completed') { - return new FormattedEvent('tool_result', `${toolName} completed`); - } else if (status === 'failed') { - return new FormattedEvent('tool_result', `${toolName} failed`); - } - - return null; - } - - case 'command_execution': { - const cmd = item.command; - const truncated = cmd.length > 50 ? cmd.substring(0, 50) + '...' : cmd; - return new FormattedEvent('thinking', `Executing: ${truncated}`); - } - - case 'file_change': { - const count = item.changes.length; - return new FormattedEvent( - 'thinking', - `Modified ${count} file${count !== 1 ? 's' : ''}`, - ); - } - - case 'web_search': { - const query = item.query; - const truncated = - query.length > 50 ? query.substring(0, 50) + '...' : query; - return new FormattedEvent('thinking', `Searching: ${truncated}`); - } - - case 'todo_list': { - const todoItems = item.items - .map(i => `${i.completed ? '- [x]' : '- [ ]'} ${i.text}`) - .join('\n'); - return new FormattedEvent('thinking', todoItems); - } - - case 'error': - return new FormattedEvent('error', item.message); - - default: - return null; - } - } - - /** - * Create heartbeat/processing event - */ - static createProcessingEvent(): FormattedEvent { - return new FormattedEvent('thinking', 'Processing...'); - } - - /** - * Clean tool name by removing MCP prefixes - */ - private static cleanToolName(name: string): string { - return name - .replace(/^mcp__[^_]+__/, '') - .replace(/^browseros-controller__/, '') - .replace(/_/g, ' '); - } -} diff --git a/packages/agent/src/agent/CodexSDKAgent.ts b/packages/agent/src/agent/CodexSDKAgent.ts deleted file mode 100644 index 66017f2..0000000 --- a/packages/agent/src/agent/CodexSDKAgent.ts +++ /dev/null @@ -1,572 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {accessSync, constants as fsConstants} from 'node:fs'; -import {dirname, join} from 'node:path'; - -import {Codex, Thread, type McpServerConfig} from '@browseros/codex-sdk-ts'; -import {logger} from '@browseros/common'; -import type {ControllerBridge} from '@browseros/controller-server'; -import {allControllerTools} from '@browseros/tools/controller-based'; - -import {AGENT_SYSTEM_PROMPT} from './Agent.prompt.js'; -import {BaseAgent} from './BaseAgent.js'; -import {CodexEventFormatter} from './CodexSDKAgent.formatter.js'; -import { - type BrowserOSCodexConfig, - writeBrowserOSCodexConfig, - writePromptFile, -} from './CodexSDKAgent.config.js'; -import {type AgentConfig, FormattedEvent} from './types.js'; - -/** - * Codex SDK specific default configuration - */ -const CODEX_SDK_DEFAULTS = { - maxTurns: 100, - mcpServerHost: '127.0.0.1', - mcpServerPort: 9100, -} as const; - -/** - * Build MCP server configuration from agent config - */ -function buildMcpServerConfig(config: AgentConfig): McpServerConfig { - const port = config.mcpServerPort || CODEX_SDK_DEFAULTS.mcpServerPort; - const mcpServerUrl = `http://${CODEX_SDK_DEFAULTS.mcpServerHost}:${port}/mcp`; - return {url: mcpServerUrl} as McpServerConfig; -} - -/** - * Codex SDK Agent implementation - * - * Wraps @openai/codex-sdk with: - * - In-process SDK MCP server with controller tools - * - Shared ControllerBridge for browseros-controller connection - * - Event formatting via EventFormatter (Codex → FormattedEvent) - * - Break-loop abort pattern (Codex has no native abort) - * - Heartbeat mechanism for long-running operations - * - Thread-based execution model - * - Metadata tracking - * - * Environment Variables: - * - CODEX_BINARY_PATH: Optional override when no bundled codex binary is found - * - * Configuration (via AgentConfig): - * - resourcesDir: Resources directory (required) - * - mcpServerPort: MCP server port (optional, defaults to 9100) - * - apiKey: OpenAI API key (required) - * - baseUrl: Custom LLM endpoint (optional) - * - modelName: Model to use (optional, defaults to 'o4-mini') - */ -export class CodexSDKAgent extends BaseAgent { - private abortController: AbortController | null = null; - private codex: Codex | null = null; - private codexExecutablePath: string | null = null; - private codexConfigPath: string | null = null; - private currentThread: Thread | null = null; - - constructor(config: AgentConfig, _controllerBridge: ControllerBridge) { - const mcpServerConfig = buildMcpServerConfig(config); - - logger.info('🔧 CodexSDKAgent initializing', { - mcpServerUrl: mcpServerConfig.url, - toolCount: allControllerTools.length, - }); - - super('codex-sdk', config, { - systemPrompt: AGENT_SYSTEM_PROMPT, - mcpServers: {'browseros-mcp': mcpServerConfig}, - maxTurns: CODEX_SDK_DEFAULTS.maxTurns, - }); - - logger.info('✅ CodexSDKAgent initialized successfully'); - } - - /** - * Initialize agent - use config passed in constructor - */ - override async init(): Promise { - this.codexExecutablePath = this.resolveCodexExecutablePath(); - - logger.info('🚀 Resolved Codex binary path', { - codexExecutablePath: this.codexExecutablePath, - }); - - if (!this.config.apiKey) { - throw new Error('API key is required in AgentConfig'); - } - - logger.info('✅ Using config from AgentConfig', { - model: this.config.modelName, - }); - - await super.init(); - this.generateCodexConfig(); - this.initializeCodex(); - } - - private generateCodexConfig(): void { - const outputDir = this.config.executionDir; - const port = this.config.mcpServerPort || CODEX_SDK_DEFAULTS.mcpServerPort; - const modelName = this.config.modelName; - const baseUrl = this.config.baseUrl; - - const codexConfig: BrowserOSCodexConfig = { - model_name: modelName, - ...(baseUrl && {base_url: baseUrl}), - api_key_env: 'BROWSEROS_API_KEY', - wire_api: 'chat', - base_instructions_file: 'browseros_prompt.md', - mcp_servers: { - browseros: { - url: `http://127.0.0.1:${port}/mcp`, - startup_timeout_sec: 30.0, - tool_timeout_sec: 120.0, - }, - }, - }; - - writePromptFile(AGENT_SYSTEM_PROMPT, outputDir); - this.codexConfigPath = writeBrowserOSCodexConfig(codexConfig, outputDir); - - logger.info('✅ Generated Codex configuration files', { - outputDir, - configPath: this.codexConfigPath, - modelName, - baseUrl, - }); - } - - private initializeCodex(): void { - const codexConfig: any = { - codexPathOverride: this.codexExecutablePath, - apiKey: this.config.apiKey, - // Note: baseUrl is not passed here because when using browseros config, - // it's already specified in the TOML file (base_url field) - }; - - this.codex = new Codex(codexConfig); - - logger.info('✅ Codex SDK initialized', { - binaryPath: this.codexExecutablePath, - }); - } - - private isExecutableFile(path: string): boolean { - try { - accessSync(path, fsConstants.X_OK); - return true; - } catch { - return false; - } - } - - private resolveCodexExecutablePath(): string { - const codexBinaryName = - process.platform === 'win32' ? 'codex.exe' : 'codex'; - - // Check CODEX_BINARY_PATH env var first - if (process.env.CODEX_BINARY_PATH) { - const envPath = process.env.CODEX_BINARY_PATH; - if (this.isExecutableFile(envPath)) { - return envPath; - } - logger.warn( - 'CODEX_BINARY_PATH set but file not found or not executable', - { - path: envPath, - }, - ); - } - - // Check resourcesDir if provided - if (this.config.resourcesDir) { - const resourcesCodexPath = join( - this.config.resourcesDir, - 'bin', - codexBinaryName, - ); - if (this.isExecutableFile(resourcesCodexPath)) { - return resourcesCodexPath; - } - } - - // Check bundled codex in current binary directory - const currentBinaryDirectory = dirname(process.execPath); - const bundledCodexPath = join(currentBinaryDirectory, codexBinaryName); - if (this.isExecutableFile(bundledCodexPath)) { - return bundledCodexPath; - } - - throw new Error( - 'Codex binary not found. Set CODEX_BINARY_PATH or --resources-dir', - ); - } - - /** - * Wrapper around iterator.next() that yields heartbeat events while waiting - * @param iterator - The async iterator - * @yields Heartbeat events (FormattedEvent) while waiting, then the final iterator result (IteratorResult) - */ - private async *nextWithHeartbeat( - iterator: AsyncIterator, - ): AsyncGenerator { - const heartbeatInterval = 20000; // 20 seconds - let heartbeatTimer: NodeJS.Timeout | null = null; - let abortHandler: (() => void) | null = null; - - // Call iterator.next() once - this generator wraps a single next() call - const iteratorPromise = iterator.next(); - - // Create abort promise - const abortPromise = new Promise((_, reject) => { - if (this.abortController) { - abortHandler = () => { - reject(new Error('Agent execution aborted by client')); - }; - this.abortController.signal.addEventListener('abort', abortHandler, { - once: true, - }); - } - }); - - try { - // Loop until the iterator promise resolves, yielding heartbeats while waiting - while (true) { - // Check if execution was aborted - if (this.abortController?.signal.aborted) { - logger.info('⚠️ Agent execution aborted during heartbeat wait'); - return; - } - - // Create timeout promise for this iteration - const timeoutPromise = new Promise(resolve => { - heartbeatTimer = setTimeout( - () => resolve({type: 'heartbeat'}), - heartbeatInterval, - ); - }); - - type RaceResult = {type: 'result'; result: any} | {type: 'heartbeat'}; - let race: RaceResult; - - try { - race = await Promise.race([ - iteratorPromise.then(result => ({type: 'result' as const, result})), - timeoutPromise.then(() => ({type: 'heartbeat' as const})), - abortPromise, - ]); - } catch (abortError) { - // Abort was triggered during wait - logger.info( - '⚠️ Agent execution aborted (caught during iterator wait)', - ); - // Break loop to stop iteration (Codex has no native abort) - return; - } - - // Clear the timeout if it was set - if (heartbeatTimer) { - clearTimeout(heartbeatTimer); - heartbeatTimer = null; - } - - if (race.type === 'heartbeat') { - // Heartbeat timeout occurred - yield processing event and continue waiting - yield CodexEventFormatter.createProcessingEvent(); - // Loop continues - will race the same iteratorPromise (still pending) vs new timeout - } else { - // Iterator result arrived - yield it and exit this generator - yield race.result; - return; - } - } - } finally { - // Clean up heartbeat timer - if (heartbeatTimer) { - clearTimeout(heartbeatTimer); - } - - // Clean up abort listener if it wasn't triggered - if ( - abortHandler && - this.abortController && - !this.abortController.signal.aborted - ) { - this.abortController.signal.removeEventListener('abort', abortHandler); - } - } - } - - /** - * Execute a task using Codex SDK and stream formatted events - * - * @param message - User's natural language request - * @yields FormattedEvent instances - */ - async *execute(message: string): AsyncGenerator { - if (!this.initialized) { - await this.init(); - } - - if (!this.codex) { - throw new Error('Codex instance not initialized'); - } - - this.startExecution(); - this.abortController = new AbortController(); - - logger.info('🤖 CodexSDKAgent executing', { - message, - }); - - try { - logger.debug('🔧 MCP Servers configured', { - count: Object.keys(this.config.mcpServers || {}).length, - servers: Object.keys(this.config.mcpServers || {}), - }); - - // Start thread with browseros config or MCP servers - const modelName = this.config.modelName; - const threadOptions: any = { - skipGitRepoCheck: true, - workingDirectory: this.config.executionDir, - }; - - // Use TOML config if available, otherwise fall back to direct MCP server config - if (this.codexConfigPath) { - threadOptions.browserosConfigPath = this.codexConfigPath; - logger.debug('📡 Starting Codex thread with browseros config', { - configPath: this.codexConfigPath, - }); - } else { - threadOptions.mcpServers = this.config.mcpServers; - threadOptions.model = modelName; - logger.debug('📡 Starting Codex thread with MCP servers', { - mcpServerCount: Object.keys(this.config.mcpServers || {}).length, - model: modelName, - }); - } - - // Reuse existing thread for follow-up messages, or create new one - // CRITICAL: Check both existence AND thread ID (ID is null if cancelled before thread.started event) - if (!this.currentThread || !this.currentThread.id) { - this.currentThread = this.codex.startThread(threadOptions); - logger.info('🆕 Created new thread for session'); - } else { - logger.info('♻️ Reusing existing thread for follow-up message', { - threadId: this.currentThread.id, - }); - } - const thread = this.currentThread; - - // Get streaming events from thread - const messages: Array<{type: 'text'; text: string}> = []; - - // When using TOML config, system prompt comes from base_instructions_file - // Otherwise, add it as first message - if (!this.codexConfigPath && this.config.systemPrompt) { - messages.push({type: 'text' as const, text: this.config.systemPrompt}); - } - - // Add user message - messages.push({type: 'text' as const, text: message}); - - const {events} = await thread.runStreamed(messages); - - // Create iterator for streaming - const iterator = events[Symbol.asyncIterator](); - - // Track last agent message for completion - let lastAgentMessage: string | null = null; - - try { - // Stream events with heartbeat and abort handling - while (true) { - // Check if execution was aborted (break-loop pattern) - if (this.abortController?.signal.aborted) { - logger.info( - '⚠️ Agent execution aborted by client (breaking loop)', - ); - // Clear thread - next message will create fresh thread - this.currentThread = null; - logger.debug('🔄 Cleared thread reference due to abort'); - break; - } - - let result: IteratorResult | null = null; - - // Iterate through heartbeat generator to get the actual result - for await (const item of this.nextWithHeartbeat(iterator)) { - if (item && item.done !== undefined) { - // This is the final result - result = item; - } else { - // This is a heartbeat/processing event - update time to prevent timeout - this.updateEventTime(); - yield item; - } - } - - if (!result || result.done) break; - - const event = result.value; - - // Log Codex events for debugging (console view truncates automatically) - if (event.type === 'error' || event.type === 'turn.failed') { - logger.error('Codex event', event); - } else { - logger.debug('Codex event', event); - } - - // Update event time - this.updateEventTime(); - - // Track last agent_message for completion - if ( - event.type === 'item.completed' && - event.item?.type === 'agent_message' - ) { - lastAgentMessage = event.item.text || null; - } - - // Track tool executions from item.completed events with mcp_tool_call type - if ( - event.type === 'item.completed' && - event.item?.type === 'mcp_tool_call' && - event.item.status === 'completed' - ) { - this.updateToolsExecuted(1); - } - - // Handle turn completion - re-emit last agent message as completion - if (event.type === 'turn.completed') { - this.updateTurns(1); - - // Log usage statistics - if (event.usage) { - logger.info('📊 Turn completed', { - inputTokens: event.usage.input_tokens, - cachedInputTokens: event.usage.cached_input_tokens, - outputTokens: event.usage.output_tokens, - }); - } - - // Re-emit last agent message as completion event - if (lastAgentMessage) { - logger.info('✅ Emitting final completion message'); - yield new FormattedEvent('completion', lastAgentMessage); - } - - // Break the loop - turn is complete - break; - } - - // Format the event using CodexEventFormatter - const formattedEvent = CodexEventFormatter.format(event); - - // Yield formatted event if valid - if (formattedEvent) { - logger.debug('📤 CodexSDKAgent yielding event', { - type: formattedEvent.type, - originalType: event.type, - }); - yield formattedEvent; - } - } - } finally { - // CRITICAL: Close iterator to trigger SIGKILL in forked SDK's finally block - // Fire-and-forget to avoid blocking markIdle() - subprocess cleanup can happen async - if (iterator.return) { - logger.debug('🔒 Closing iterator to terminate Codex subprocess'); - iterator.return(undefined).catch((error) => { - logger.warn('⚠️ Iterator cleanup error (non-fatal)', { - error: error instanceof Error ? error.message : String(error), - }); - }); - } - } - - // Complete execution tracking - this.completeExecution(); - - logger.info('✅ CodexSDKAgent execution complete', { - turns: this.metadata.turns, - toolsExecuted: this.metadata.toolsExecuted, - duration: Date.now() - this.executionStartTime, - }); - } catch (error) { - // Clear thread on error - next call will create fresh thread - this.currentThread = null; - logger.debug('🔄 Cleared thread reference due to error'); - - // Mark execution error - this.errorExecution( - error instanceof Error ? error : new Error(String(error)), - ); - - logger.error('❌ CodexSDKAgent execution failed', { - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, - }); - - throw error; - } finally { - // Clear AbortController reference - this.abortController = null; - } - } - - /** - * Abort current execution - * Triggers abort signal to stop the current task gracefully - */ - abort(): void { - if (this.abortController) { - logger.info('🛑 Aborting CodexSDKAgent execution'); - this.abortController.abort(); - } - } - - /** - * Check if agent is currently executing - */ - isExecuting(): boolean { - return this.metadata.state === 'executing' && this.abortController !== null; - } - - /** - * Cleanup agent resources - * - * Immediately kills the Codex subprocess using SIGKILL. - * Does NOT close shared ControllerBridge. - */ - async destroy(): Promise { - if (this.isDestroyed()) { - logger.debug('⚠️ CodexSDKAgent already destroyed'); - return; - } - - this.markDestroyed(); - - // Clear thread reference - this.currentThread = null; - - // Trigger abort controller for cleanup - if (this.abortController) { - this.abortController.abort(); - await new Promise(resolve => setTimeout(resolve, 100)); - } - - // DO NOT close ControllerBridge - it's shared and owned by main server - - logger.debug('🗑️ CodexSDKAgent destroyed', { - totalDuration: this.metadata.totalDuration, - turns: this.metadata.turns, - toolsExecuted: this.metadata.toolsExecuted, - }); - } -} diff --git a/packages/agent/src/agent/ControllerToolsAdapter.ts b/packages/agent/src/agent/ControllerToolsAdapter.ts deleted file mode 100644 index 50be00f..0000000 --- a/packages/agent/src/agent/ControllerToolsAdapter.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {tool, createSdkMcpServer} from '@anthropic-ai/claude-agent-sdk'; -import {logger} from '@browseros/common'; -import type {ToolDefinition} from '@browseros/tools'; -import {ControllerResponse} from '@browseros/tools/controller-based'; -import type {Context} from '@browseros/tools/controller-based'; - -/** - * Convert a controller tool to Claude SDK MCP tool format - */ -function adaptControllerTool( - toolDef: ToolDefinition, - context: Context, -) { - return tool( - toolDef.name, - toolDef.description, - toolDef.schema, - async (args, _extra) => { - logger.debug(`🔧 Executing controller tool: ${toolDef.name}`, {args}); - - try { - // Create request and response objects - const request = {params: args}; - const response = new ControllerResponse(); - - // Execute the tool handler - await toolDef.handler(request, response, context); - - // Convert response to CallToolResult format - const content = response.toContent(); - - return {content}; - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - logger.error(`❌ Controller tool ${toolDef.name} failed`, { - error: errorMsg, - }); - - return { - content: [ - { - type: 'text' as const, - text: `Error: ${errorMsg}`, - }, - ], - isError: true, - }; - } - }, - ); -} - -/** - * Create an in-process SDK MCP server with all controller tools - * - * @param tools - Array of controller tool definitions - * @param context - Controller context for executing actions - * @returns SDK MCP server configuration - */ -export function createControllerMcpServer( - tools: Array>, - context: Context, -) { - // Adapt all controller tools to SDK format - const sdkTools = tools.map(tool => adaptControllerTool(tool, context)); - - logger.info( - `🔧 Creating SDK MCP server with ${sdkTools.length} controller tools`, - ); - - // Create and return the SDK MCP server - return createSdkMcpServer({ - name: 'browseros-controller', - version: '1.0.0', - tools: sdkTools, - }); -} diff --git a/packages/agent/src/agent/GeminiAgent.ts b/packages/agent/src/agent/GeminiAgent.ts new file mode 100644 index 0000000..fa019fa --- /dev/null +++ b/packages/agent/src/agent/GeminiAgent.ts @@ -0,0 +1,217 @@ +import { + Config as GeminiConfig, + MCPServerConfig, + GeminiEventType, + executeToolCall, + type GeminiClient, + type ToolCallRequestInfo, +} from '@google/gemini-cli-core'; +import type { Part } from '@google/genai'; +import { logger, fetchBrowserOSConfig, getLLMConfigFromProvider } from '@browseros/common'; +import { VercelAIContentGenerator, AIProvider } from './gemini-vercel-sdk-adapter/index.js'; +import type { HonoSSEStream } from './gemini-vercel-sdk-adapter/types.js'; +import { AgentExecutionError } from '../errors.js'; +import type { AgentConfig } from './types.js'; + +const MAX_TURNS = 100; + +interface McpHttpServerOptions { + httpUrl: string; + headers?: Record; + trust?: boolean; +} + +// MCP Server Config for HTTP is a positional argument in the constructor (can't be passed as an object) +function createHttpMcpServerConfig(options: McpHttpServerOptions): MCPServerConfig { + return new MCPServerConfig( + undefined, // command (stdio) + undefined, // args (stdio) + undefined, // env (stdio) + undefined, // cwd (stdio) + undefined, // url (sse transport) + options.httpUrl, // httpUrl (streamable http) + options.headers, // headers + undefined, // tcp (websocket) + undefined, // timeout + options.trust, // trust + ); +} + +export class GeminiAgent { + private constructor( + private client: GeminiClient, + private geminiConfig: GeminiConfig, + private contentGenerator: VercelAIContentGenerator, + private conversationId: string, + ) {} + + static async create(config: AgentConfig): Promise { + const tempDir = config.tempDir; + + // If provider is BROWSEROS, fetch config from BROWSEROS_CONFIG_URL + let resolvedConfig = { ...config }; + if (config.provider === AIProvider.BROWSEROS) { + const configUrl = process.env.BROWSEROS_CONFIG_URL; + if (!configUrl) { + throw new Error('BROWSEROS_CONFIG_URL environment variable is required for BrowserOS provider'); + } + + logger.info('Fetching BrowserOS config', { configUrl }); + const browserosConfig = await fetchBrowserOSConfig(configUrl); + const llmConfig = getLLMConfigFromProvider(browserosConfig, 'default'); + + resolvedConfig = { + ...config, + model: llmConfig.modelName, + apiKey: llmConfig.apiKey, + baseUrl: llmConfig.baseUrl, + }; + + logger.info('Using BrowserOS config', { + model: resolvedConfig.model, + baseUrl: resolvedConfig.baseUrl, + }); + } + + const modelString = `${resolvedConfig.provider}/${resolvedConfig.model}`; + + const geminiConfig = new GeminiConfig({ + sessionId: resolvedConfig.conversationId, + targetDir: tempDir, + cwd: tempDir, + debugMode: false, + model: modelString, + excludeTools: ['run_shell_command', 'write_file', 'replace'], + mcpServers: resolvedConfig.mcpServerUrl + ? { + 'browseros-mcp': createHttpMcpServerConfig({ + httpUrl: resolvedConfig.mcpServerUrl, + headers: { 'Accept': 'application/json, text/event-stream' }, + trust: true, + }), + } + : undefined, + }); + + await geminiConfig.initialize(); + + console.log('resolvedConfig', resolvedConfig); + const contentGenerator = new VercelAIContentGenerator(resolvedConfig); + + (geminiConfig as unknown as { contentGenerator: VercelAIContentGenerator }).contentGenerator = contentGenerator; + + const client = geminiConfig.getGeminiClient(); + await client.setTools(); + + logger.info('GeminiAgent created', { + conversationId: resolvedConfig.conversationId, + provider: resolvedConfig.provider, + model: resolvedConfig.model, + }); + + return new GeminiAgent(client, geminiConfig, contentGenerator, resolvedConfig.conversationId); + } + + getHistory() { + return this.client.getHistory(); + } + + async execute(message: string, honoStream: HonoSSEStream, signal?: AbortSignal): Promise { + this.contentGenerator.setHonoStream(honoStream); + + const abortSignal = signal || new AbortController().signal; + const promptId = `${this.conversationId}-${Date.now()}`; + + let currentParts: Part[] = [{ text: message }]; + let turnCount = 0; + + logger.info('Starting agent execution', { + conversationId: this.conversationId, + message: message.substring(0, 100), + historyLength: this.client.getHistory().length, + }); + + while (true) { + turnCount++; + logger.debug(`Turn ${turnCount}`, { conversationId: this.conversationId }); + + if (turnCount > MAX_TURNS) { + logger.warn('Max turns exceeded', { + conversationId: this.conversationId, + turnCount, + }); + break; + } + + const toolCallRequests: ToolCallRequestInfo[] = []; + + const responseStream = this.client.sendMessageStream( + currentParts, + abortSignal, + promptId, + ); + + for await (const event of responseStream) { + if (abortSignal.aborted) { + break; + } + + if (event.type === GeminiEventType.ToolCallRequest) { + toolCallRequests.push(event.value as ToolCallRequestInfo); + } else if (event.type === GeminiEventType.Error) { + const errorValue = event.value as { error: Error }; + throw new AgentExecutionError('Agent execution failed', errorValue.error); + } + // Other events are handled by the content generator + } + + if (toolCallRequests.length > 0) { + logger.debug(`Executing ${toolCallRequests.length} tool(s)`, { + conversationId: this.conversationId, + tools: toolCallRequests.map((r) => r.name), + }); + + const toolResponseParts: Part[] = []; + + for (const requestInfo of toolCallRequests) { + try { + const completedToolCall = await executeToolCall( + this.geminiConfig, + requestInfo, + abortSignal, + ); + + const toolResponse = completedToolCall.response; + + if (toolResponse.error) { + logger.warn('Tool execution error', { + conversationId: this.conversationId, + tool: requestInfo.name, + error: toolResponse.error.message, + }); + } + + if (toolResponse.responseParts) { + toolResponseParts.push(...(toolResponse.responseParts as Part[])); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error('Tool execution failed', { + conversationId: this.conversationId, + tool: requestInfo.name, + error: errorMessage, + }); + } + } + + currentParts = toolResponseParts; + } else { + logger.info('Agent execution complete', { + conversationId: this.conversationId, + totalTurns: turnCount, + }); + break; + } + } + } +} diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts index 8323fc9..b2af847 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts @@ -8,7 +8,7 @@ * Multi-provider LLM adapter using Vercel AI SDK */ -import { streamText, generateText, convertToModelMessages } from 'ai'; +import { streamText, generateText } from 'ai'; import { createAnthropic } from '@ai-sdk/anthropic'; import { createOpenAI } from '@ai-sdk/openai'; import { createGoogleGenerativeAI } from '@ai-sdk/google'; @@ -41,7 +41,7 @@ import type { VercelAIConfig } from './types.js'; * Implements ContentGenerator interface using strategy pattern for conversions */ export class VercelAIContentGenerator implements ContentGenerator { - private providerRegistry: Map unknown>; + private providerInstance: (modelId: string) => unknown; private model: string; private honoStream?: HonoSSEStream; @@ -52,16 +52,22 @@ export class VercelAIContentGenerator implements ContentGenerator { constructor(config: VercelAIConfig) { this.model = config.model; - this.honoStream = config.honoStream; - this.providerRegistry = new Map(); // Initialize conversion strategies this.toolStrategy = new ToolConversionStrategy(); this.messageStrategy = new MessageConversionStrategy(); this.responseStrategy = new ResponseConversionStrategy(this.toolStrategy); - // Register providers based on config - this.registerProviders(config); + // Register the single provider from config + this.providerInstance = this.createProvider(config); + } + + /** + * Set/override the Hono SSE stream for the current request + * This allows reusing the same ContentGenerator across multiple requests + */ + setHonoStream(stream: HonoSSEStream | undefined): void { + this.honoStream = stream; } /** @@ -79,13 +85,8 @@ export class VercelAIContentGenerator implements ContentGenerator { request.config?.systemInstruction, ); - const { provider, modelName } = this.parseModel( - request.model || this.model, - ); - const providerInstance = this.getProvider(provider); - const result = await generateText({ - model: providerInstance(modelName) as Parameters< + model: this.providerInstance(this.model) as Parameters< typeof generateText >[0]['model'], messages, @@ -112,13 +113,8 @@ export class VercelAIContentGenerator implements ContentGenerator { request.config?.systemInstruction, ); - const { provider, modelName } = this.parseModel( - request.model || this.model, - ); - const providerInstance = this.getProvider(provider); - const result = streamText({ - model: providerInstance(modelName) as Parameters< + model: this.providerInstance(this.model) as Parameters< typeof streamText >[0]['model'], messages, @@ -175,138 +171,88 @@ export class VercelAIContentGenerator implements ContentGenerator { } /** - * Register providers based on config + * Create provider instance based on config */ - private registerProviders(config: VercelAIConfig): void { - const providers = config.providers || {}; - - const anthropicConfig = providers[AIProvider.ANTHROPIC]; - if (anthropicConfig?.apiKey) { - this.providerRegistry.set( - AIProvider.ANTHROPIC, - createAnthropic({ apiKey: anthropicConfig.apiKey }), - ); - } - - const openaiConfig = providers[AIProvider.OPENAI]; - if (openaiConfig?.apiKey) { - this.providerRegistry.set( - AIProvider.OPENAI, - createOpenAI({ - apiKey: openaiConfig.apiKey, - compatibility: 'strict', - }), - ); - } + private createProvider(config: VercelAIConfig): (modelId: string) => unknown { + switch (config.provider) { + case AIProvider.ANTHROPIC: + if (!config.apiKey) { + throw new Error('Anthropic provider requires apiKey'); + } + return createAnthropic({ apiKey: config.apiKey }); - const googleConfig = providers[AIProvider.GOOGLE]; - if (googleConfig?.apiKey) { - this.providerRegistry.set( - AIProvider.GOOGLE, - createGoogleGenerativeAI({ apiKey: googleConfig.apiKey }), - ); - } + case AIProvider.OPENAI: + if (!config.apiKey) { + throw new Error('OpenAI provider requires apiKey'); + } + return createOpenAI({ apiKey: config.apiKey }); - const openrouterConfig = providers[AIProvider.OPENROUTER]; - if (openrouterConfig?.apiKey) { - this.providerRegistry.set( - AIProvider.OPENROUTER, - createOpenRouter({ apiKey: openrouterConfig.apiKey }), - ); - } + case AIProvider.GOOGLE: + if (!config.apiKey) { + throw new Error('Google provider requires apiKey'); + } + return createGoogleGenerativeAI({ apiKey: config.apiKey }); - const azureConfig = providers[AIProvider.AZURE]; - if (azureConfig?.apiKey && azureConfig.resourceName) { - this.providerRegistry.set( - AIProvider.AZURE, - createAzure({ - resourceName: azureConfig.resourceName, - apiKey: azureConfig.apiKey, - }), - ); - } + case AIProvider.OPENROUTER: + if (!config.apiKey) { + throw new Error('OpenRouter provider requires apiKey'); + } + return createOpenRouter({ apiKey: config.apiKey }); - const lmstudioConfig = providers[AIProvider.LMSTUDIO]; - if (lmstudioConfig !== undefined) { - this.providerRegistry.set( - AIProvider.LMSTUDIO, - createOpenAICompatible({ + case AIProvider.AZURE: + if (!config.apiKey || !config.resourceName) { + throw new Error('Azure provider requires apiKey and resourceName'); + } + return createAzure({ + resourceName: config.resourceName, + apiKey: config.apiKey, + }); + + case AIProvider.LMSTUDIO: + if (!config.baseUrl) { + throw new Error('LMStudio provider requires baseUrl'); + } + return createOpenAICompatible({ name: 'lmstudio', - baseURL: lmstudioConfig.baseUrl || 'http://localhost:1234/v1', - }), - ); - } + baseURL: config.baseUrl, + }); - const ollamaConfig = providers[AIProvider.OLLAMA]; - if (ollamaConfig !== undefined) { - this.providerRegistry.set( - AIProvider.OLLAMA, - createOpenAICompatible({ + case AIProvider.OLLAMA: + if (!config.baseUrl) { + throw new Error('Ollama provider requires baseUrl'); + } + return createOpenAICompatible({ name: 'ollama', - baseURL: ollamaConfig.baseUrl || 'http://localhost:11434/v1', - }), - ); - } - - const bedrockConfig = providers[AIProvider.BEDROCK]; - if ( - bedrockConfig?.accessKeyId && - bedrockConfig.secretAccessKey && - bedrockConfig.region - ) { - this.providerRegistry.set( - AIProvider.BEDROCK, - createAmazonBedrock({ - region: bedrockConfig.region, - accessKeyId: bedrockConfig.accessKeyId, - secretAccessKey: bedrockConfig.secretAccessKey, - sessionToken: bedrockConfig.sessionToken, - }), - ); - } - } - - /** - * Parse model string into provider and model name - */ - private parseModel(modelString: string): { - provider: string; - modelName: string; - } { - const parts = modelString.split('/'); - - if (parts.length < 2) { - throw new Error( - `Invalid model format: "${modelString}". ` + - `Expected "provider/model-name" (e.g., "anthropic/claude-3-5-sonnet-20241022")`, - ); - } - - const provider = parts[0]; - const modelName = parts.slice(1).join('/'); - - return { provider, modelName }; - } - - /** - * Get provider instance or throw error - */ - private getProvider(provider: string): (modelId: string) => unknown { - const providerInstance = this.providerRegistry.get(provider); + baseURL: config.baseUrl, + }); - if (!providerInstance) { - const available = Array.from(this.providerRegistry.keys()).join(', '); - throw new Error( - `Provider "${provider}" not configured. ` + - `Available providers: ${available || 'none'}. ` + - `Configure it in config.providers.${provider}`, - ); + case AIProvider.BEDROCK: + if (!config.accessKeyId || !config.secretAccessKey || !config.region) { + throw new Error('Bedrock provider requires accessKeyId, secretAccessKey, and region'); + } + return createAmazonBedrock({ + region: config.region, + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + sessionToken: config.sessionToken, + }); + + case AIProvider.BROWSEROS: + if (!config.baseUrl || !config.apiKey) { + throw new Error('BrowserOS provider requires baseUrl and apiKey'); + } + return createOpenAICompatible({ + name: 'browseros', + baseURL: config.baseUrl, + apiKey: config.apiKey, + }); + + default: + throw new Error(`Unknown provider: ${config.provider}`); } - - return providerInstance; } } // Re-export types for consumers export { AIProvider }; -export type { VercelAIConfig, ProviderConfig, HonoSSEStream } from './types.js'; +export type { VercelAIConfig, HonoSSEStream } from './types.js'; diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.ts index f71688a..f0950c5 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/message.ts @@ -10,10 +10,10 @@ */ import type { - CoreMessage, VercelContentPart, - LanguageModelV2ToolResultOutput, } from '../types.js'; +import type { CoreMessage } from 'ai'; +import type { LanguageModelV2ToolResultOutput, JSONValue } from '@ai-sdk/provider'; import type { Content, ContentUnion } from '@google/genai'; import { isTextPart, @@ -247,22 +247,21 @@ export class MessageConversionStrategy { // Check for error first if (typeof response === 'object' && 'error' in response && response.error) { - output = { - type: typeof response.error === 'string' ? 'error-text' : 'error-json', - value: response.error, - }; + const errorValue = response.error; + output = typeof errorValue === 'string' + ? { type: 'error-text', value: errorValue } + : { type: 'error-json', value: errorValue as JSONValue }; } else if (typeof response === 'object' && 'output' in response) { // Gemini's explicit output format: {output: value} - output = { - type: typeof response.output === 'string' ? 'text' : 'json', - value: response.output, - }; + const outputValue = response.output; + output = typeof outputValue === 'string' + ? { type: 'text', value: outputValue } + : { type: 'json', value: outputValue as JSONValue }; } else { // Whole response is the output - output = { - type: typeof response === 'string' ? 'text' : 'json', - value: response, - }; + output = typeof response === 'string' + ? { type: 'text', value: response } + : { type: 'json', value: response as JSONValue }; } return { diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts index 83bfb06..6f064da 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts @@ -10,10 +10,9 @@ * Handles both streaming and non-streaming responses */ -import { GenerateContentResponse, FinishReason } from '@google/genai'; +import { GenerateContentResponse, FinishReason, Part, FunctionCall } from '@google/genai' +import { formatDataStreamPart } from '@ai-sdk/ui-utils'; import type { - Part, - FunctionCall, VercelFinishReason, VercelUsage, HonoSSEStream, @@ -103,7 +102,7 @@ export class ResponseConversionStrategy { { toolCallId: string; toolName: string; - args: unknown; + input: unknown; } >(); @@ -134,12 +133,10 @@ export class ResponseConversionStrategy { const delta = chunk.text; textAccumulator += delta; - // Emit v5 SSE format to frontend: text-delta event - // v5 uses 'text' property, not 'textDelta' (v4) + // Emit AI SDK format: 0:"text" if (honoStream) { try { - const sseData = `data: ${JSON.stringify({ type: 'text-delta', text: delta })}\n\n`; - await honoStream.write(sseData); + await honoStream.write(formatDataStreamPart('text', delta)); } catch { // Failed to write to stream } @@ -157,16 +154,14 @@ export class ResponseConversionStrategy { ], } as GenerateContentResponse; } else if (chunk.type === 'tool-call') { - // Emit v5 SSE format to frontend: tool-call event + // Emit AI SDK format: 9:{"toolCallId":"...","toolName":"...","args":{...}} if (honoStream) { try { - const sseData = `data: ${JSON.stringify({ - type: 'tool-call', + await honoStream.write(formatDataStreamPart('tool_call', { toolCallId: chunk.toolCallId, toolName: chunk.toolName, - input: chunk.input, - })}\n\n`; - await honoStream.write(sseData); + args: chunk.input, + })); } catch { // Failed to write to stream } @@ -191,28 +186,6 @@ export class ResponseConversionStrategy { usage = this.estimateUsage(textAccumulator); } - // Emit final finish event in v5 SSE format - if (honoStream && (finishReason || usage)) { - try { - const finishData: any = { type: 'finish' }; - if (finishReason) { - finishData.finishReason = finishReason; - } - if (usage) { - finishData.usage = { - promptTokens: usage.promptTokens || 0, - completionTokens: usage.completionTokens || 0, - totalTokens: usage.totalTokens || 0, - }; - } - - const sseData = `data: ${JSON.stringify(finishData)}\n\n`; - await honoStream.write(sseData); - } catch { - // Failed to write to stream - } - } - // Yield final response with tool calls and metadata if (toolCallsMap.size > 0 || finishReason || usage) { const parts: Part[] = []; diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts index b8c7f91..4e1065a 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/tool.ts @@ -10,13 +10,13 @@ */ import type { - FunctionCall, - FunctionDeclaration, VercelTool, } from '../types.js'; -import { jsonSchema, VercelToolCallSchema } from '../types.js'; + +import { jsonSchema } from 'ai'; import { ConversionError } from '../errors.js'; -import type { ToolListUnion } from '@google/genai'; +import type { ToolListUnion, FunctionDeclaration, FunctionCall } from '@google/genai'; +import { VercelToolCallSchema } from '../types.js'; export class ToolConversionStrategy { /** diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts index 08affa7..8a69afc 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/types.ts @@ -11,27 +11,8 @@ import { z } from 'zod'; import { jsonSchema } from 'ai'; - -// Re-export for use in strategies -export { jsonSchema }; - -// === Re-export SDK Types === - // Vercel AI SDK -export type { CoreMessage } from 'ai'; -export type { LanguageModelV2ToolResultOutput } from '@ai-sdk/provider'; - -// Gemini SDK -export type { - Part, - FunctionCall, - FunctionDeclaration, - FunctionResponse, - Tool, - Content, - GenerateContentResponse, - FinishReason, -} from '@google/genai'; +import type { LanguageModelV2ToolResultOutput } from '@ai-sdk/provider'; // === Vercel SDK Runtime Shapes (What We Receive) === @@ -228,28 +209,25 @@ export enum AIProvider { OLLAMA = 'ollama', LMSTUDIO = 'lmstudio', BEDROCK = 'bedrock', + BROWSEROS = 'browseros', } /** - * Provider-specific configuration + * Zod schema for Vercel AI adapter configuration + * Single source of truth - use z.infer for the type */ -export interface ProviderConfig { - apiKey?: string; - baseUrl?: string; +export const VercelAIConfigSchema = z.object({ + provider: z.nativeEnum(AIProvider), + model: z.string().min(1, 'Model name is required'), + apiKey: z.string().optional(), + baseUrl: z.string().optional(), // Azure-specific - resourceName?: string; + resourceName: z.string().optional(), // AWS Bedrock-specific - region?: string; - accessKeyId?: string; - secretAccessKey?: string; - sessionToken?: string; -} + region: z.string().optional(), + accessKeyId: z.string().optional(), + secretAccessKey: z.string().optional(), + sessionToken: z.string().optional(), +}); -/** - * Configuration for Vercel AI adapter - */ -export interface VercelAIConfig { - model: string; - providers?: Partial>; - honoStream?: HonoSSEStream; -} +export type VercelAIConfig = z.infer; \ No newline at end of file diff --git a/packages/agent/src/agent/index.ts b/packages/agent/src/agent/index.ts new file mode 100644 index 0000000..53429a3 --- /dev/null +++ b/packages/agent/src/agent/index.ts @@ -0,0 +1,4 @@ +export { GeminiAgent } from './GeminiAgent.js'; +export type { AgentConfig } from './types.js'; +export { VercelAIContentGenerator, AIProvider } from './gemini-vercel-sdk-adapter/index.js'; +export type { VercelAIConfig, HonoSSEStream } from './gemini-vercel-sdk-adapter/index.js'; diff --git a/packages/agent/src/agent/registry.ts b/packages/agent/src/agent/registry.ts deleted file mode 100644 index c5ad172..0000000 --- a/packages/agent/src/agent/registry.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {AgentFactory} from './AgentFactory.js'; -import {ClaudeSDKAgent} from './ClaudeSDKAgent.js'; -import {CodexSDKAgent} from './CodexSDKAgent.js'; - -/** - * Register all available agents - * - * This should be called once at application startup to register - * all agent types with the factory. - */ -export function registerAgents(): void { - AgentFactory.register( - 'codex-sdk', - CodexSDKAgent, - 'Codex SDK agent for OpenAI Codex integration', - ); - - AgentFactory.register( - 'claude-sdk', - ClaudeSDKAgent, - 'Claude SDK agent for Anthropic Claude integration', - ); -} diff --git a/packages/agent/src/agent/types.ts b/packages/agent/src/agent/types.ts index c6fb175..49a5d26 100644 --- a/packages/agent/src/agent/types.ts +++ b/packages/agent/src/agent/types.ts @@ -1,159 +1,10 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ +import { z } from 'zod'; +import { VercelAIConfigSchema } from './gemini-vercel-sdk-adapter/types.js'; -import {z} from 'zod'; - -/** - * Formatted event structure for WebSocket clients - */ -export class FormattedEvent { - type: - | 'init' - | 'thinking' - | 'tool_use' - | 'tool_result' - | 'response' - | 'completion' - | 'error' - | 'processing'; - content: string; - metadata?: { - turnCount?: number; - isError?: boolean; - duration?: number; - deniedTools?: number; - }; - - constructor( - type: FormattedEvent['type'], - content: string, - metadata?: FormattedEvent['metadata'], - ) { - this.type = type; - this.content = content; - this.metadata = metadata; - } - - toJSON() { - return { - type: this.type, - content: this.content, - ...(this.metadata && {metadata: this.metadata}), - }; - } -} - -/** - * Configuration for agent initialization - * - * Contains all parameters needed to create and configure an agent - */ -export const AgentConfigSchema = z.object({ - /** - * Resources directory path - used for binary storage and static resources - * Required - serves as the primary directory for binaries - */ - resourcesDir: z.string().min(1, 'Resources directory is required'), - - /** - * Execution directory path - used for logs, configs, and working directory - * Always set (normalized to resourcesDir if not explicitly provided) - */ - executionDir: z.string().min(1), - - /** - * MCP server port (optional, defaults to 9100) - */ - mcpServerPort: z.number().positive().optional(), - - /** - * API key for the agent SDK (Anthropic, OpenAI, etc.) - * Optional - can be provided via environment variables or config URL - */ - apiKey: z.string().optional(), - - /** - * Base URL for custom LLM endpoints - */ - baseUrl: z.string().url(), - - /** - * Model name/identifier to use - */ - modelName: z.string(), - - /** - * Maximum conversation turns before stopping - * Default: 100 - */ - maxTurns: z.number().positive().optional(), - - /** - * Maximum thinking tokens (for models that support extended thinking) - * Default: 10000 - */ - maxThinkingTokens: z.number().positive().optional(), - - /** - * System prompt to guide agent behavior - * Optional - agents have their own default prompts - */ - systemPrompt: z.string().optional(), - - /** - * MCP servers configuration (handled internally by agents) - * Optional - agents configure their own MCP servers - */ - mcpServers: z.record(z.string(), z.any()).optional(), -}); - -export type AgentConfig = z.infer; - -/** - * Runtime metadata about agent execution state - */ -export const AgentMetadataSchema = z.object({ - /** - * Agent type identifier (e.g., 'claude-sdk') - */ - type: z.string(), - - /** - * Current turn count - */ - turns: z.number().nonnegative(), - - /** - * Total execution time in milliseconds (across all execute() calls) - */ - totalDuration: z.number().nonnegative(), - - /** - * Timestamp of last event emitted - */ - lastEventTime: z.number().positive(), - - /** - * Number of tools executed - */ - toolsExecuted: z.number().nonnegative(), - - /** - * Current agent state - */ - state: z.enum(['idle', 'executing', 'error', 'destroyed']), - - /** - * Error message if state is 'error' - */ - error: z.string().optional(), - - /** - * Agent-specific custom metadata - */ - custom: z.record(z.string(), z.unknown()).optional(), +export const AgentConfigSchema = VercelAIConfigSchema.extend({ + conversationId: z.string(), + tempDir: z.string(), + mcpServerUrl: z.string().optional(), }); -export type AgentMetadata = z.infer; +export type AgentConfig = z.infer; \ No newline at end of file From 31a1ea62d17c005fe7167cb10f42179263eada11 Mon Sep 17 00:00:00 2001 From: shivammittal274 Date: Thu, 27 Nov 2025 00:05:56 +0530 Subject: [PATCH 6/7] session management and http server code --- bun.lock | 81 ++- package.json | 3 + packages/agent/package.json | 6 +- packages/agent/src/errors.ts | 64 ++ packages/agent/src/http/HttpServer.ts | 171 ++++++ packages/agent/src/http/index.ts | 3 + packages/agent/src/http/types.ts | 30 + packages/agent/src/index.ts | 26 +- .../agent/src/session/SessionManager.test.ts | 203 ------- packages/agent/src/session/SessionManager.ts | 524 +---------------- packages/agent/src/session/index.ts | 1 + packages/agent/src/websocket/protocol.ts | 142 ----- packages/agent/src/websocket/server.ts | 547 ------------------ packages/agent/tsconfig.json | 2 +- packages/server/src/main.ts | 114 +--- 15 files changed, 414 insertions(+), 1503 deletions(-) create mode 100644 packages/agent/src/errors.ts create mode 100644 packages/agent/src/http/HttpServer.ts create mode 100644 packages/agent/src/http/index.ts create mode 100644 packages/agent/src/http/types.ts delete mode 100644 packages/agent/src/session/SessionManager.test.ts create mode 100644 packages/agent/src/session/index.ts delete mode 100644 packages/agent/src/websocket/protocol.ts delete mode 100644 packages/agent/src/websocket/server.ts diff --git a/bun.lock b/bun.lock index 029b72b..72a0da1 100644 --- a/bun.lock +++ b/bun.lock @@ -16,6 +16,8 @@ "smol-toml": "^1.4.2", }, "devDependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/ui-utils": "^1.2.11", "@eslint/js": "^9.35.0", "@modelcontextprotocol/sdk": "1.20.0", "@stylistic/eslint-plugin": "^5.4.0", @@ -27,6 +29,7 @@ "@types/sinon": "^17.0.4", "@typescript-eslint/eslint-plugin": "^8.43.0", "@typescript-eslint/parser": "^8.43.0", + "ai": "^5.0.102", "async-mutex": "^0.5.0", "chrome-devtools-frontend": "1.0.1524741", "commander": "^14.0.1", @@ -66,11 +69,14 @@ "@ai-sdk/google": "^2.0.43", "@ai-sdk/openai": "^2.0.72", "@ai-sdk/openai-compatible": "^1.0.27", + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/ui-utils": "^1.2.11", "@anthropic-ai/claude-agent-sdk": "^0.1.11", "@browseros/common": "workspace:*", "@browseros/server": "workspace:*", "@browseros/tools": "workspace:*", "@google/gemini-cli-core": "^0.16.0", + "@hono/node-server": "^1.19.6", "@openrouter/ai-sdk-provider": "~1.2.5", "ai": "^5.0.101", "zod": "^4.1.12", @@ -78,6 +84,7 @@ "devDependencies": { "@types/bun": "latest", "typescript": "^5.9.3", + "vitest": "^4.0.14", }, "optionalDependencies": { "chrome-devtools-mcp": "latest", @@ -222,7 +229,9 @@ "@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="], + + "@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.2.11", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="], "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.1.23", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^3.24.1" } }, "sha512-DktXOjzS2hOuuj2Zpo7fEooONfMa5bm09pt1/Vt4vn30YugELoezn/ZQ/TG5uSQ7+Zl/ElxAvi4vGDorj1Tirg=="], @@ -422,6 +431,8 @@ "@grpc/proto-loader": ["@grpc/proto-loader@0.8.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" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], + "@hono/node-server": ["@hono/node-server@1.19.6", "", { "peerDependencies": { "hono": "^4" } }, "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], @@ -732,10 +743,14 @@ "@types/caseless": ["@types/caseless@0.12.5", "", {}, "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg=="], + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + "@types/chrome": ["@types/chrome@0.1.24", "", { "dependencies": { "@types/filesystem": "*", "@types/har-format": "*" } }, "sha512-9iO9HL2bMeGS4C8m6gNFWUyuPE5HEUFk+rGh+7oriUjg+ata4Fc9PoVlu8xvGm7yoo3AmS3J6fAjoFj61NL2rw=="], "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="], "@types/eslint-scope": ["@types/eslint-scope@3.7.7", "", { "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg=="], @@ -858,6 +873,20 @@ "@vercel/oidc": ["@vercel/oidc@3.0.5", "", {}, "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw=="], + "@vitest/expect": ["@vitest/expect@4.0.14", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.14", "@vitest/utils": "4.0.14", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw=="], + + "@vitest/mocker": ["@vitest/mocker@4.0.14", "", { "dependencies": { "@vitest/spy": "4.0.14", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.0.14", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ=="], + + "@vitest/runner": ["@vitest/runner@4.0.14", "", { "dependencies": { "@vitest/utils": "4.0.14", "pathe": "^2.0.3" } }, "sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.0.14", "", { "dependencies": { "@vitest/pretty-format": "4.0.14", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag=="], + + "@vitest/spy": ["@vitest/spy@4.0.14", "", {}, "sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg=="], + + "@vitest/utils": ["@vitest/utils@4.0.14", "", { "dependencies": { "@vitest/pretty-format": "4.0.14", "tinyrainbow": "^3.0.3" } }, "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw=="], + "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="], @@ -916,7 +945,7 @@ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - "ai": ["ai@5.0.101", "", { "dependencies": { "@ai-sdk/gateway": "2.0.15", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/P4fgs2PGYTBaZi192YkPikOudsl9vccA65F7J7LvoNTOoP5kh1yAsJPsKAy6FXU32bAngai7ft1UDyC3u7z5g=="], + "ai": ["ai@5.0.102", "", { "dependencies": { "@ai-sdk/gateway": "2.0.15", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-snRK3nS5DESOjjpq7S74g8YszWVMzjagfHqlJWZsbtl9PyOS+2XUd8dt2wWg/jdaq/jh0aU66W1mx5qFjUQyEg=="], "ajv": ["ajv@6.12.6", "", { "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" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], @@ -952,6 +981,8 @@ "arrify": ["arrify@2.0.1", "", {}, "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], @@ -1046,6 +1077,8 @@ "caniuse-lite": ["caniuse-lite@1.0.30001751", "", {}, "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw=="], + "chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], @@ -1260,6 +1293,8 @@ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], @@ -1282,6 +1317,8 @@ "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="], + "expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="], + "express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.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" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="], "express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="], @@ -1794,6 +1831,8 @@ "obliterator": ["obliterator@2.0.5", "", {}, "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], @@ -1976,6 +2015,8 @@ "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], + "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], + "selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="], "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], @@ -2010,6 +2051,8 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "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" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "simple-git": ["simple-git@3.30.0", "", { "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", "debug": "^4.4.0" } }, "sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg=="], @@ -2048,8 +2091,12 @@ "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "stream-events": ["stream-events@1.0.5", "", { "dependencies": { "stubs": "^3.0.0" } }, "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg=="], @@ -2110,10 +2157,14 @@ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="], + "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], @@ -2204,6 +2255,10 @@ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + "vite": ["vite@7.2.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.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" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w=="], + + "vitest": ["vitest@4.0.14", "", { "dependencies": { "@vitest/expect": "4.0.14", "@vitest/mocker": "4.0.14", "@vitest/pretty-format": "4.0.14", "@vitest/runner": "4.0.14", "@vitest/snapshot": "4.0.14", "@vitest/spy": "4.0.14", "@vitest/utils": "4.0.14", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.14", "@vitest/browser-preview": "4.0.14", "@vitest/browser-webdriverio": "4.0.14", "@vitest/ui": "4.0.14", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw=="], + "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], "watchpack": ["watchpack@2.4.4", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA=="], @@ -2234,6 +2289,8 @@ "which-typed-array": ["which-typed-array@1.1.19", "", { "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" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="], + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "wildcard": ["wildcard@2.0.1", "", {}, "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], @@ -2276,6 +2333,24 @@ "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="], + "@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + + "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + + "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + + "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + + "@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + + "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + + "@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="], + + "@ai-sdk/ui-utils/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="], + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -2346,6 +2421,8 @@ "accepts/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], + "ajv-formats/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], "ajv-keywords/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], diff --git a/package.json b/package.json index a396a6d..f089bf3 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,8 @@ "smol-toml": "^1.4.2" }, "devDependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/ui-utils": "^1.2.11", "@eslint/js": "^9.35.0", "@modelcontextprotocol/sdk": "1.20.0", "@stylistic/eslint-plugin": "^5.4.0", @@ -69,6 +71,7 @@ "@types/sinon": "^17.0.4", "@typescript-eslint/eslint-plugin": "^8.43.0", "@typescript-eslint/parser": "^8.43.0", + "ai": "^5.0.102", "async-mutex": "^0.5.0", "chrome-devtools-frontend": "1.0.1524741", "commander": "^14.0.1", diff --git a/packages/agent/package.json b/packages/agent/package.json index 586553d..846d4f2 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -34,18 +34,22 @@ "@ai-sdk/google": "^2.0.43", "@ai-sdk/openai": "^2.0.72", "@ai-sdk/openai-compatible": "^1.0.27", + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/ui-utils": "^1.2.11", "@anthropic-ai/claude-agent-sdk": "^0.1.11", "@browseros/common": "workspace:*", "@browseros/server": "workspace:*", "@browseros/tools": "workspace:*", "@google/gemini-cli-core": "^0.16.0", + "@hono/node-server": "^1.19.6", "@openrouter/ai-sdk-provider": "~1.2.5", "ai": "^5.0.101", "zod": "^4.1.12" }, "devDependencies": { "@types/bun": "latest", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.0.14" }, "optionalDependencies": { "chrome-devtools-mcp": "latest" diff --git a/packages/agent/src/errors.ts b/packages/agent/src/errors.ts new file mode 100644 index 0000000..cb2af91 --- /dev/null +++ b/packages/agent/src/errors.ts @@ -0,0 +1,64 @@ +export class HttpAgentError extends Error { + constructor( + message: string, + public statusCode: number = 500, + public code?: string, + ) { + super(message); + this.name = this.constructor.name; + Error.captureStackTrace(this, this.constructor); + } + + toJSON() { + return { + error: { + name: this.name, + message: this.message, + code: this.code, + statusCode: this.statusCode, + }, + }; + } +} + +export class ValidationError extends HttpAgentError { + constructor(message: string, public details?: unknown) { + super(message, 400, 'VALIDATION_ERROR'); + } + + override toJSON() { + return { + error: { + name: this.name, + message: this.message, + code: this.code, + statusCode: this.statusCode, + details: this.details, + }, + }; + } +} + +export class SessionNotFoundError extends HttpAgentError { + constructor(public conversationId: string) { + super(`Session "${conversationId}" not found.`, 404, 'SESSION_NOT_FOUND'); + } +} + +export class AgentExecutionError extends HttpAgentError { + constructor(message: string, public originalError?: Error) { + super(message, 500, 'AGENT_EXECUTION_ERROR'); + } + + override toJSON() { + return { + error: { + name: this.name, + message: this.message, + code: this.code, + statusCode: this.statusCode, + originalError: this.originalError?.message, + }, + }; + } +} diff --git a/packages/agent/src/http/HttpServer.ts b/packages/agent/src/http/HttpServer.ts new file mode 100644 index 0000000..2f887e2 --- /dev/null +++ b/packages/agent/src/http/HttpServer.ts @@ -0,0 +1,171 @@ +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { stream } from 'hono/streaming'; +import { serve } from '@hono/node-server'; +import { formatDataStreamPart } from '@ai-sdk/ui-utils'; +import { logger } from '@browseros/common'; +import type { Context, Next } from 'hono'; +import type { ContentfulStatusCode } from 'hono/utils/http-status'; +import type { z } from 'zod'; + +import { SessionManager } from '../session/SessionManager.js'; +import { HttpAgentError, ValidationError, AgentExecutionError } from '../errors.js'; +import { ChatRequestSchema, HttpServerConfigSchema } from './types.js'; +import type { HttpServerConfig, ValidatedHttpServerConfig, ChatRequest } from './types.js'; + +type AppVariables = { + validatedBody: unknown; +}; + +const DEFAULT_MCP_SERVER_URL = 'http://127.0.0.1:9150/mcp'; +const DEFAULT_TEMP_DIR = '/tmp'; + +function validateRequest(schema: z.ZodType) { + return async (c: Context<{ Variables: AppVariables }>, next: Next) => { + try { + const body = await c.req.json(); + const validated = schema.parse(body); + c.set('validatedBody', validated); + await next(); + } catch (err) { + if (err && typeof err === 'object' && 'issues' in err) { + const zodError = err as { issues: unknown }; + logger.warn('Request validation failed', { issues: zodError.issues }); + throw new ValidationError('Request validation failed', zodError.issues); + } + throw err; + } + }; +} + +export function createHttpServer(config: HttpServerConfig) { + const validatedConfig: ValidatedHttpServerConfig = HttpServerConfigSchema.parse(config); + const mcpServerUrl = validatedConfig.mcpServerUrl || process.env.MCP_SERVER_URL || DEFAULT_MCP_SERVER_URL; + + const app = new Hono<{ Variables: AppVariables }>(); + const sessionManager = new SessionManager(); + + app.use( + '/*', + cors({ + origin: validatedConfig.corsOrigins, + allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'], + allowHeaders: ['Content-Type', 'Authorization'], + }), + ); + + app.onError((err, c) => { + const error = err as Error; + + if (error instanceof HttpAgentError) { + logger.warn('HTTP Agent Error', { + name: error.name, + message: error.message, + code: error.code, + statusCode: error.statusCode, + }); + return c.json(error.toJSON(), error.statusCode as ContentfulStatusCode); + } + + logger.error('Unhandled Error', { + message: error.message, + stack: error.stack, + }); + + return c.json( + { + error: { + name: 'InternalServerError', + message: error.message || 'An unexpected error occurred', + code: 'INTERNAL_SERVER_ERROR', + statusCode: 500, + }, + }, + 500, + ); + }); + + app.get('/health', (c) => c.json({ status: 'ok' })); + + app.post('/chat', validateRequest(ChatRequestSchema), async (c) => { + const request = c.get('validatedBody') as ChatRequest; + + logger.info('Chat request received', { + conversationId: request.conversationId, + provider: request.provider, + model: request.model, + }); + + c.header('Content-Type', 'text/plain; charset=utf-8'); + c.header('X-Vercel-AI-Data-Stream', 'v1'); + c.header('Cache-Control', 'no-cache'); + c.header('Connection', 'keep-alive'); + + return stream(c, async (honoStream) => { + try { + const agent = await sessionManager.getOrCreate({ + conversationId: request.conversationId, + provider: request.provider, + model: request.model, + apiKey: request.apiKey, + baseUrl: request.baseUrl, + // Azure-specific + resourceName: request.resourceName, + // AWS Bedrock-specific + region: request.region, + accessKeyId: request.accessKeyId, + secretAccessKey: request.secretAccessKey, + sessionToken: request.sessionToken, + // Agent-specific + tempDir: validatedConfig.tempDir || DEFAULT_TEMP_DIR, + mcpServerUrl, + }); + + await agent.execute(request.message, honoStream); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Agent execution failed'; + logger.error('Agent execution error', { + conversationId: request.conversationId, + error: errorMessage, + }); + await honoStream.write(formatDataStreamPart('error', errorMessage)); + throw new AgentExecutionError('Agent execution failed', error instanceof Error ? error : undefined); + } + }); + }); + + app.delete('/chat/:conversationId', (c) => { + const conversationId = c.req.param('conversationId'); + const deleted = sessionManager.delete(conversationId); + + if (deleted) { + return c.json({ + success: true, + message: `Session ${conversationId} deleted`, + sessionCount: sessionManager.count(), + }); + } + + return c.json({ + success: false, + message: `Session ${conversationId} not found`, + }, 404); + }); + + const server = serve({ + fetch: app.fetch, + port: validatedConfig.port, + hostname: validatedConfig.host, + }); + + logger.info('HTTP Agent Server started', { + port: validatedConfig.port, + host: validatedConfig.host, + }); + + return { + app, + server, + config: validatedConfig, + }; +} diff --git a/packages/agent/src/http/index.ts b/packages/agent/src/http/index.ts new file mode 100644 index 0000000..58ed9a8 --- /dev/null +++ b/packages/agent/src/http/index.ts @@ -0,0 +1,3 @@ +export { createHttpServer } from './HttpServer.js'; +export { HttpServerConfigSchema, ChatRequestSchema } from './types.js'; +export type { HttpServerConfig, ValidatedHttpServerConfig, ChatRequest } from './types.js'; diff --git a/packages/agent/src/http/types.ts b/packages/agent/src/http/types.ts new file mode 100644 index 0000000..6da0379 --- /dev/null +++ b/packages/agent/src/http/types.ts @@ -0,0 +1,30 @@ +import { z } from 'zod'; +import { VercelAIConfigSchema } from '../agent/gemini-vercel-sdk-adapter/types.js'; + +/** + * Chat request schema extends VercelAIConfig with request-specific fields + */ +export const ChatRequestSchema = VercelAIConfigSchema.extend({ + conversationId: z.string().uuid(), + message: z.string().min(1, 'Message cannot be empty'), +}); + +export type ChatRequest = z.infer; + +export interface HttpServerConfig { + port: number; + host?: string; + corsOrigins?: string[]; + tempDir?: string; + mcpServerUrl?: string; +} + +export const HttpServerConfigSchema = z.object({ + port: z.number().int().positive(), + host: z.string().optional().default('0.0.0.0'), + corsOrigins: z.array(z.string()).optional().default(['*']), + tempDir: z.string().optional().default('/tmp'), + mcpServerUrl: z.string().optional(), +}); + +export type ValidatedHttpServerConfig = z.infer; diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 27a47a1..1b483c3 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -1,16 +1,14 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ +export { createHttpServer } from './http/index.js'; +export { HttpServerConfigSchema, ChatRequestSchema } from './http/index.js'; +export type { HttpServerConfig, ValidatedHttpServerConfig, ChatRequest } from './http/index.js'; -// Public API exports for integration with main server -export {createServer as createAgentServer} from './websocket/server.js'; -export {ServerConfigSchema as AgentServerConfigSchema} from './websocket/server.js'; -export type {ServerConfig as AgentServerConfig} from './websocket/server.js'; -export type {ControllerBridge} from '@browseros/controller-server'; +// Alias for backwards compatibility with packages/server +export { createHttpServer as createAgentServer } from './http/index.js'; +export type { HttpServerConfig as AgentServerConfig } from './http/index.js'; -// Agent factory exports -export {AgentFactory} from './agent/AgentFactory.js'; -export type {AgentConstructor} from './agent/AgentFactory.js'; -export {registerAgents} from './agent/registry.js'; -export {BaseAgent} from './agent/BaseAgent.js'; +export { GeminiAgent, AIProvider } from './agent/index.js'; +export type { AgentConfig } from './agent/index.js'; + +export { SessionManager } from './session/index.js'; + +export { HttpAgentError, ValidationError, SessionNotFoundError, AgentExecutionError } from './errors.js'; diff --git a/packages/agent/src/session/SessionManager.test.ts b/packages/agent/src/session/SessionManager.test.ts deleted file mode 100644 index ec602df..0000000 --- a/packages/agent/src/session/SessionManager.test.ts +++ /dev/null @@ -1,203 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {describe, it, expect, beforeEach, afterEach} from 'bun:test'; - -import type {FormattedEvent} from '../utils/EventFormatter.js'; - -import {BaseAgent} from '../agent/BaseAgent.js'; -import type {AgentConfig} from '../agent/types.js'; -import {AgentFactory} from '../agent/AgentFactory.js'; -import {SessionManager} from './SessionManager.js'; - -// Test agent implementation -class TestAgent extends BaseAgent { - constructor(config: AgentConfig) { - super('test-agent', config); - } - - async *execute(message: string): AsyncGenerator { - yield {type: 'test', content: message, metadata: {}} as any; - } - - async destroy(): Promise { - this.markDestroyed(); - } -} - -describe('SessionManager-unit-test', () => { - let sessionManager: SessionManager; - let mockControllerBridge: any; - - beforeEach(() => { - // Register test agent - AgentFactory.register('test-agent', TestAgent as any, 'Test agent'); - - // Create fresh instance for each test - sessionManager = new SessionManager({ - maxSessions: 5, - idleTimeoutMs: 60000, - }); - }); - - afterEach(() => { - // Clean up agent registry - AgentFactory.clear(); - }); - - // Unit Test 1 - Creation and initialization - it('tests that SessionManager creates with correct initial state', () => { - expect(sessionManager).toBeDefined(); - - // Verify private fields are initialized - expect(sessionManager['sessions']).toBeInstanceOf(Map); - expect(sessionManager['agents']).toBeInstanceOf(Map); - expect(sessionManager['sessions'].size).toBe(0); - expect(sessionManager['agents'].size).toBe(0); - - // Verify config is stored - expect(sessionManager['config'].maxSessions).toBe(5); - expect(sessionManager['config'].idleTimeoutMs).toBe(60000); - }); - - // Unit Test 2 - Session creation and state management - it('tests that session creates and updates state correctly', () => { - const agentConfig = { - resourcesDir: '/test/resources', - executionDir: '/test/execution', - apiKey: 'test-key', - }; - - // Check initial state - expect(sessionManager['sessions'].size).toBe(0); - expect(sessionManager.isAtCapacity()).toBe(false); - - // Create session - const session = sessionManager.createSession( - {id: crypto.randomUUID(), agentType: 'test-agent'}, - agentConfig, - ); - - // Verify state changes - expect(session).toBeDefined(); - expect(session.id).toBeDefined(); - expect(session.messageCount).toBe(0); - expect(sessionManager['sessions'].size).toBe(1); - expect(sessionManager['agents'].size).toBe(1); - - // Verify capacity calculation - const capacity = sessionManager.getCapacity(); - expect(capacity.active).toBe(1); - expect(capacity.available).toBe(4); - }); - - // Unit Test 3 - Session state transitions - it('tests that session state transitions handle correctly', () => { - const sessionId = crypto.randomUUID(); - const agentConfig = { - resourcesDir: '/test/resources', - executionDir: '/test/execution', - apiKey: 'test-key', - }; - - // Create session - sessionManager.createSession( - {id: sessionId, agentType: 'test-agent'}, - agentConfig, - ); - const session = sessionManager['sessions'].get(sessionId); - - // Initial state should be IDLE - expect(session?.state).toBe('idle'); - - // Mark as processing - const marked = sessionManager.markProcessing(sessionId); - expect(marked).toBe(true); - expect(session?.state).toBe('processing'); - expect(session?.messageCount).toBe(1); - - // Try to mark as processing again (should fail) - const markedAgain = sessionManager.markProcessing(sessionId); - expect(markedAgain).toBe(false); - expect(session?.messageCount).toBe(1); // Should not increment - - // Mark as idle - sessionManager.markIdle(sessionId); - expect(session?.state).toBe('idle'); - expect(session?.lastActivity).toBeGreaterThan(0); - }); - - // Unit Test 4 - Idle session detection - it('tests that idle sessions identify correctly', async () => { - const sessionId = crypto.randomUUID(); - const agentConfig = { - resourcesDir: '/test/resources', - executionDir: '/test/execution', - apiKey: 'test-key', - }; - - // Create session with short idle timeout - const shortTimeoutManager = new SessionManager( - { - maxSessions: 5, - idleTimeoutMs: 100, // 100ms - }, - mockControllerBridge, - ); - - shortTimeoutManager.createSession( - {id: sessionId, agentType: 'test-agent'}, - agentConfig, - ); - - // Mark as idle - shortTimeoutManager.markIdle(sessionId); - - // Initially should not be idle - let idleSessions = shortTimeoutManager.findIdleSessions(); - expect(idleSessions).toHaveLength(0); - - // Wait for timeout - await new Promise(resolve => setTimeout(resolve, 150)); - - // Now should be detected as idle - idleSessions = shortTimeoutManager.findIdleSessions(); - expect(idleSessions).toHaveLength(1); - expect(idleSessions[0]).toBe(sessionId); - - // Cleanup - await shortTimeoutManager.deleteSession(sessionId); - }); - - // Unit Test 5 - Capacity management - it('tests that capacity limits enforce correctly', () => { - const smallManager = new SessionManager( - { - maxSessions: 2, - idleTimeoutMs: 60000, - }, - mockControllerBridge, - ); - - const agentConfig = { - resourcesDir: '/test/resources', - executionDir: '/test/execution', - apiKey: 'test-key', - }; - - // Create first session - smallManager.createSession({agentType: 'test-agent'}, agentConfig); - expect(smallManager.isAtCapacity()).toBe(false); - - // Create second session - smallManager.createSession({agentType: 'test-agent'}, agentConfig); - expect(smallManager.isAtCapacity()).toBe(true); - - // Try to create third session (should throw) - expect(() => { - smallManager.createSession({agentType: 'test-agent'}, agentConfig); - }).toThrow('Server at capacity'); - }); -}); diff --git a/packages/agent/src/session/SessionManager.ts b/packages/agent/src/session/SessionManager.ts index 3f50583..e2bcb1f 100644 --- a/packages/agent/src/session/SessionManager.ts +++ b/packages/agent/src/session/SessionManager.ts @@ -1,516 +1,48 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ +import { logger } from '@browseros/common'; +import { GeminiAgent } from '../agent/GeminiAgent.js'; +import type { AgentConfig } from '../agent/types.js'; -import {logger} from '@browseros/common'; -import type {ControllerBridge} from '@browseros/controller-server'; -import {z} from 'zod'; - -import {AgentFactory} from '../agent/AgentFactory.js'; -import type {BaseAgent} from '../agent/BaseAgent.js'; -import type {AgentConfig} from '../agent/types.js'; - - -/** - * Session state enum - */ -enum SessionState { - IDLE = 'idle', // Connected, waiting for messages - PROCESSING = 'processing', // Actively processing a message - CLOSING = 'closing', // Cleanup initiated - CLOSED = 'closed', // Fully closed -} - -/** - * Session data model schema - * Note: Does NOT store WebSocket reference to prevent memory leaks - */ -const SessionSchema = z.object({ - id: z.string().uuid(), - userId: z.string().optional(), // Klavis user ID for MCP integration - state: z.nativeEnum(SessionState), - createdAt: z.number().positive(), - lastActivity: z.number().positive(), - messageCount: z.number().nonnegative(), -}); - -type Session = z.infer; - -/** - * Session metrics for monitoring - */ -const SessionMetricsSchema = z.object({ - totalSessions: z.number().nonnegative(), - activeSessions: z.number().nonnegative(), - idleSessions: z.number().nonnegative(), - processingSessions: z.number().nonnegative(), - averageMessageCount: z.number().nonnegative(), -}); - -type SessionMetrics = z.infer; - -/** - * Session creation options - */ -const CreateSessionOptionsSchema = z.object({ - id: z.string().uuid().optional(), // Optional: specify sessionId (useful for testing) - userId: z.string().optional(), // Optional: Klavis user ID for MCP integration - agentType: z.string().min(1).optional(), // Optional: agent type (defaults to 'codex-sdk') -}); - -type CreateSessionOptions = z.infer; - -/** - * Session configuration - */ -const SessionConfigSchema = z.object({ - maxSessions: z.number().positive(), - idleTimeoutMs: z.number().positive(), -}); - -type SessionConfig = z.infer; - -/** - * SessionManager - Manages multiple concurrent WebSocket sessions - * - * Architecture: - * - Does NOT store WebSocket references (prevents memory leaks) - * - Stores session metadata only - * - Server maintains Map separately - * - Provides capacity checking and idle session detection - * - Receives shared ControllerBridge for browser extension connection - */ export class SessionManager { - private sessions: Map; - private agents: Map; - private config: SessionConfig; - private controllerBridge: ControllerBridge; - private cleanupTimerId?: Timer; - - constructor(config: SessionConfig, controllerBridge: ControllerBridge) { - this.sessions = new Map(); - this.agents = new Map(); - this.config = config; - this.controllerBridge = controllerBridge; - - logger.info('SessionManager initialized', { - maxSessions: config.maxSessions, - idleTimeoutMs: config.idleTimeoutMs, - sharedControllerBridge: true, - }); - } - - /** - * Create a new session with an agent - * - * @param options - Session creation options (includes optional agentType) - * @param agentConfig - Agent configuration - * @returns Session instance - */ - createSession( - options?: CreateSessionOptions, - agentConfig?: AgentConfig, - ): Session { - // Check capacity first - if (this.isAtCapacity()) { - throw new Error( - `Server at capacity (max ${this.config.maxSessions} sessions)`, - ); - } - - const sessionId = options?.id || crypto.randomUUID(); - const now = Date.now(); - - const session: Session = { - id: sessionId, - userId: options?.userId, - state: SessionState.IDLE, - createdAt: now, - lastActivity: now, - messageCount: 0, - }; - - // Validate with Zod - SessionSchema.parse(session); - - this.sessions.set(sessionId, session); - - // Create agent if config provided - if (agentConfig) { - try { - // Use factory to create agent (defaults to 'codex-sdk' if not specified) - const agentType = options?.agentType || 'codex-sdk'; - const agent = AgentFactory.create( - agentType, - agentConfig, - this.controllerBridge, - ); - this.agents.set(sessionId, agent); - - logger.info('Session created with agent', { - sessionId, - agentType, - totalSessions: this.sessions.size, - }); - } catch (error) { - // Cleanup session if agent creation fails - this.sessions.delete(sessionId); - - logger.error('Failed to create agent for session', { - sessionId, - error: error instanceof Error ? error.message : String(error), - }); + private sessions = new Map(); - throw error; - } - } else { - logger.info('Session created without agent', { - sessionId, - totalSessions: this.sessions.size, - }); - } - - return session; - } + async getOrCreate(config: AgentConfig): Promise { + const existing = this.sessions.get(config.conversationId); - /** - * Get a session by ID - */ - getSession(sessionId: string): Session | undefined { - return this.sessions.get(sessionId); - } - - /** - * Check if a session exists - */ - hasSession(sessionId: string): boolean { - return this.sessions.has(sessionId); - } - - /** - * Get agent for a session - * - * @param sessionId - Session ID - * @returns BaseAgent instance or undefined if not found - */ - getAgent(sessionId: string): BaseAgent | undefined { - return this.agents.get(sessionId); - } - - /** - * Get user ID for a session - * - * @param sessionId - Session ID - * @returns User ID or undefined if not set - */ - getUserId(sessionId: string): string | undefined { - return this.sessions.get(sessionId)?.userId; - } - - /** - * Update session activity timestamp - */ - updateActivity(sessionId: string): void { - const session = this.sessions.get(sessionId); - if (!session) { - logger.warn('Attempted to update activity for non-existent session', { - sessionId, + if (existing) { + logger.info('Reusing existing session', { + conversationId: config.conversationId, + historyLength: existing.getHistory().length, }); - return; - } - - session.lastActivity = Date.now(); - - logger.debug('Session activity updated', { - sessionId, - messageCount: session.messageCount, - }); - } - - /** - * Mark session as processing a message - * Note: Does NOT update lastActivity - idle timer only starts after completion - */ - markProcessing(sessionId: string): boolean { - const session = this.sessions.get(sessionId); - if (!session) { - return false; - } - - // Reject if already processing (prevent concurrent message handling) - if (session.state === SessionState.PROCESSING) { - logger.warn('Session already processing message', {sessionId}); - return false; + return existing; } - session.state = SessionState.PROCESSING; - session.messageCount++; - // ❌ Removed: session.lastActivity = Date.now() - // Idle timer starts from markIdle(), not here + const agent = await GeminiAgent.create(config); + this.sessions.set(config.conversationId, agent); - logger.debug('Session marked as processing', { - sessionId, - messageCount: session.messageCount, + logger.info('Session added to manager', { + conversationId: config.conversationId, + totalSessions: this.sessions.size, }); - return true; - } - - /** - * Mark session as idle (done processing) - * Updates lastActivity - starts the idle timeout countdown - */ - markIdle(sessionId: string): void { - const session = this.sessions.get(sessionId); - if (!session) { - return; - } - - session.state = SessionState.IDLE; - session.lastActivity = Date.now(); // ✅ Idle timer starts here - - logger.debug('Session marked as idle', {sessionId}); + return agent; } - /** - * Cancel current execution for a session - * Triggers abort on the agent if it's executing - * CRITICAL: Does NOT mark session as idle - let processMessage() handle that - * - * @param sessionId - Session ID - * @returns true if cancel was triggered, false if not executing or agent not found - */ - cancelExecution(sessionId: string): boolean { - const agent = this.agents.get(sessionId); - if (!agent) { - logger.warn('⚠️ Cancel requested but no agent found', {sessionId}); - return false; - } - - // Defensive: check abort support - if (typeof agent.abort !== 'function') { - logger.warn('⚠️ Agent does not support cancel', { - sessionId, - agentType: agent.getMetadata().type, + delete(conversationId: string): boolean { + const deleted = this.sessions.delete(conversationId); + if (deleted) { + logger.info('Session deleted', { + conversationId, + remainingSessions: this.sessions.size, }); - return false; - } - - if (!agent.isExecuting()) { - logger.debug('⚠️ Cancel requested but agent not executing', {sessionId}); - return false; - } - - logger.info('🛑 Cancelling execution', {sessionId}); - agent.abort(); - - // CRITICAL: Do NOT mark idle here! - // Let the original processMessage() call mark idle when it completes - // Otherwise we get race condition: new messages can start while execute() is still in finally block - - return true; - } - - /** - * Delete a session and its agent - * - * Now async to support agent cleanup - */ - async deleteSession(sessionId: string): Promise { - const session = this.sessions.get(sessionId); - if (!session) { - return false; - } - - // Mark as closed - session.state = SessionState.CLOSED; - - // Destroy agent (NEW) - const agent = this.agents.get(sessionId); - if (agent) { - try { - await agent.destroy(); - this.agents.delete(sessionId); - logger.debug('Agent destroyed', {sessionId}); - } catch (error) { - logger.error('Failed to destroy agent', { - sessionId, - error: error instanceof Error ? error.message : String(error), - }); - // Continue with session deletion even if agent cleanup fails - } } - - // Delete session - this.sessions.delete(sessionId); - - logger.info('Session deleted', { - sessionId, - remainingSessions: this.sessions.size, - messageCount: session.messageCount, - lifetime: Date.now() - session.createdAt, - }); - - return true; + return deleted; } - /** - * Check if server is at capacity - */ - isAtCapacity(): boolean { - return this.sessions.size >= this.config.maxSessions; - } - - /** - * Get current capacity status - */ - getCapacity(): {active: number; max: number; available: number} { - const active = this.sessions.size; - const max = this.config.maxSessions; - return { - active, - max, - available: max - active, - }; - } - - /** - * Find idle sessions that have timed out - * Returns array of sessionIds to close - */ - findIdleSessions(): string[] { - const now = Date.now(); - const idleSessionIds: string[] = []; - - for (const [sessionId, session] of this.sessions) { - const idleTime = now - session.lastActivity; - - // Only cleanup sessions that are IDLE (not actively processing) - if ( - session.state === SessionState.IDLE && - idleTime > this.config.idleTimeoutMs - ) { - idleSessionIds.push(sessionId); - - logger.info('Idle session detected', { - sessionId, - idleTimeMs: idleTime, - threshold: this.config.idleTimeoutMs, - }); - } - } - - return idleSessionIds; + count(): number { + return this.sessions.size; } - /** - * Start periodic cleanup of idle sessions - * Returns cleanup function to stop the timer - */ - startCleanup(intervalMs = 60000): () => void { - if (this.cleanupTimerId) { - logger.warn('Cleanup timer already running'); - return () => {}; - } - - logger.info('Starting periodic session cleanup', {intervalMs}); - - this.cleanupTimerId = setInterval(() => { - const idleSessionIds = this.findIdleSessions(); - - if (idleSessionIds.length > 0) { - logger.info('Cleanup found idle sessions', { - count: idleSessionIds.length, - sessionIds: idleSessionIds, - }); - } - - // Note: Actual WebSocket closing happens in server.ts - // This just identifies which sessions to close - }, intervalMs); - - // Return cleanup function - return () => { - if (this.cleanupTimerId) { - clearInterval(this.cleanupTimerId); - this.cleanupTimerId = undefined; - logger.info('Session cleanup stopped'); - } - }; - } - - /** - * Get session metrics - */ - getMetrics(): SessionMetrics { - let idleCount = 0; - let processingCount = 0; - let totalMessages = 0; - - for (const session of this.sessions.values()) { - totalMessages += session.messageCount; - - if (session.state === SessionState.IDLE) { - idleCount++; - } else if (session.state === SessionState.PROCESSING) { - processingCount++; - } - } - - return { - totalSessions: this.sessions.size, - activeSessions: this.sessions.size, - idleSessions: idleCount, - processingSessions: processingCount, - averageMessageCount: - this.sessions.size > 0 ? totalMessages / this.sessions.size : 0, - }; - } - - /** - * Get all session IDs - */ - getAllSessionIds(): string[] { - return Array.from(this.sessions.keys()); - } - - /** - * Shutdown - cleanup all sessions and agents - * - * Now async to support agent cleanup - */ - async shutdown(): Promise { - logger.info('SessionManager shutting down', { - activeSessions: this.sessions.size, - activeAgents: this.agents.size, - }); - - // Stop cleanup timer - if (this.cleanupTimerId) { - clearInterval(this.cleanupTimerId); - this.cleanupTimerId = undefined; - } - - // Destroy all agents (NEW) - const destroyPromises: Array> = []; - for (const [sessionId, agent] of this.agents) { - destroyPromises.push( - agent.destroy().catch(error => { - logger.error('Failed to destroy agent during shutdown', { - sessionId, - error: error instanceof Error ? error.message : String(error), - }); - }), - ); - } - - await Promise.all(destroyPromises); - this.agents.clear(); - - // Clear all sessions - this.sessions.clear(); - - logger.info('SessionManager shutdown complete'); + has(conversationId: string): boolean { + return this.sessions.has(conversationId); } } diff --git a/packages/agent/src/session/index.ts b/packages/agent/src/session/index.ts new file mode 100644 index 0000000..7b31a78 --- /dev/null +++ b/packages/agent/src/session/index.ts @@ -0,0 +1 @@ +export { SessionManager } from './SessionManager.js'; diff --git a/packages/agent/src/websocket/protocol.ts b/packages/agent/src/websocket/protocol.ts deleted file mode 100644 index 6896192..0000000 --- a/packages/agent/src/websocket/protocol.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {z} from 'zod'; - -/** - * MESSAGE PROTOCOL - * - * Client → Server: ClientMessage - * Server → Client: ServerEvent - */ - -// ============================================================================ -// CLIENT → SERVER MESSAGES -// ============================================================================ - -/** - * Regular message from client - */ -export const ClientRegularMessageSchema = z.object({ - type: z.literal('message'), - content: z.string().min(1, 'Message content cannot be empty'), -}); - -/** - * Cancel message from client - */ -export const ClientCancelMessageSchema = z.object({ - type: z.literal('cancel'), - sessionId: z.string().optional(), -}); - -/** - * Discriminated union of all client message types - */ -export const ClientMessageSchema = z.discriminatedUnion('type', [ - ClientRegularMessageSchema, - ClientCancelMessageSchema, -]); - -export type ClientMessage = z.infer; - -// ============================================================================ -// SERVER → CLIENT EVENTS -// ============================================================================ - -/** - * Connection confirmation event - */ -export const ConnectionEventSchema = z.object({ - type: z.literal('connection'), - data: z.object({ - status: z.literal('connected'), - sessionId: z.string(), - timestamp: z.number(), - }), -}); - -export type ConnectionEvent = z.infer; - -/** - * Agent event (init, response, tool_use, tool_result, completion, error) - * Uses FormattedEvent structure from Phase 1 - */ -export const AgentEventSchema = z.object({ - type: z.enum([ - 'init', - 'thinking', - 'tool_use', - 'tool_result', - 'response', - 'completion', - 'error', - ]), - content: z.string(), -}); - -export type AgentEvent = z.infer; - -/** - * Cancelled event (acknowledgment of cancel request) - */ -export const CancelledEventSchema = z.object({ - type: z.literal('cancelled'), - sessionId: z.string(), - message: z.string().optional(), -}); - -export type CancelledEvent = z.infer; - -/** - * Error event - */ -export const ErrorEventSchema = z.object({ - type: z.literal('error'), - error: z.string(), - code: z.string().optional(), -}); - -export type ErrorEvent = z.infer; - -/** - * Union of all server event types - */ -export const ServerEventSchema = z.union([ - ConnectionEventSchema, - AgentEventSchema, - CancelledEventSchema, - ErrorEventSchema, -]); - -export type ServerEvent = z.infer; - -// ============================================================================ -// VALIDATION HELPERS -// ============================================================================ - -/** - * Validate a client message - * @throws {z.ZodError} if validation fails - */ -export function validateClientMessage(data: unknown): ClientMessage { - return ClientMessageSchema.parse(data); -} - -/** - * Try to parse a client message, returning null on error - */ -export function tryParseClientMessage(data: unknown): ClientMessage | null { - const result = ClientMessageSchema.safeParse(data); - return result.success ? result.data : null; -} - -/** - * Validate a server event - * @throws {z.ZodError} if validation fails - */ -export function validateServerEvent(data: unknown): ServerEvent { - return ServerEventSchema.parse(data); -} diff --git a/packages/agent/src/websocket/server.ts b/packages/agent/src/websocket/server.ts deleted file mode 100644 index 6569481..0000000 --- a/packages/agent/src/websocket/server.ts +++ /dev/null @@ -1,547 +0,0 @@ -/** - * @license - * Copyright 2025 BrowserOS - */ - -import {logger} from '@browseros/common'; -import type {ControllerBridge} from '@browseros/controller-server'; -import type {ServerWebSocket} from 'bun'; -import {z} from 'zod'; - -import {SessionManager} from '../session/SessionManager.js'; - - -import { - tryParseClientMessage, - type ServerEvent, - type ConnectionEvent, - type ErrorEvent, -} from './protocol.js'; - -/** - * WebSocket data stored per connection - */ -const WebSocketDataSchema = z.object({ - sessionId: z.string().uuid(), - createdAt: z.number().positive(), -}); - -type WebSocketData = z.infer; - -/** - * Server configuration schema - */ -export const ServerConfigSchema = z.object({ - port: z.number().int().min(1).max(65535), - resourcesDir: z.string().min(1, 'Resources directory is required'), - executionDir: z.string().optional(), - mcpServerPort: z.number().positive().optional(), - apiKey: z.string().optional(), - baseUrl: z.string().url().optional(), - modelName: z.string().optional(), - maxSessions: z.number().int().positive(), - idleTimeoutMs: z.number().positive(), - eventGapTimeoutMs: z.number().positive(), -}); - -export type ServerConfig = z.infer; - -/** - * Server statistics (internal, no validation needed) - */ -interface ServerStats { - startTime: number; - connections: number; - messagesProcessed: number; -} - -/** - * Global server state - */ -const stats: ServerStats = { - startTime: Date.now(), - connections: 0, - messagesProcessed: 0, -}; - -/** - * Create and start the WebSocket server - * - * @param config - Server configuration - * @param controllerBridge - Shared ControllerBridge for browser extension connection - */ -export function createServer( - config: ServerConfig, - controllerBridge: ControllerBridge, -) { - logger.info('🚀 Starting WebSocket server...', { - port: config.port, - maxSessions: config.maxSessions, - idleTimeoutMs: config.idleTimeoutMs, - eventGapTimeoutMs: config.eventGapTimeoutMs, - sharedControllerBridge: true, - }); - - // Create SessionManager with shared ControllerBridge - const sessionManager = new SessionManager( - { - maxSessions: config.maxSessions, - idleTimeoutMs: config.idleTimeoutMs, - }, - controllerBridge, - ); - - // Track WebSocket connections (needed to close idle sessions) - const wsConnections = new Map>(); - - // Cleanup idle sessions callback (now async) -> commenting out for now as we should let BrowserOS agent handle this - // const cleanupIdle = async () => { - // const idleSessionIds = sessionManager.findIdleSessions(); - - // for (const sessionId of idleSessionIds) { - // const ws = wsConnections.get(sessionId); - // if (ws) { - // logger.info('🧹 Closing idle session', {sessionId}); - // ws.close(1001, 'Idle timeout'); - // wsConnections.delete(sessionId); - // } - // await sessionManager.deleteSession(sessionId); - // } - // }; - - // // Run cleanup check with the timer - // setInterval(cleanupIdle, 60000); - - const server = Bun.serve({ - port: config.port, - - /** - * HTTP request handler (for health check and upgrade) - */ - async fetch(req, server) { - const url = new URL(req.url); - - logger.info(`${req.method} ${url.pathname}`); - - // Health check endpoint - if (url.pathname === '/health') { - return handleHealthCheck(sessionManager); - } - - // WebSocket upgrade - if (req.headers.get('upgrade') === 'websocket') { - // Check capacity BEFORE upgrading - if (sessionManager.isAtCapacity()) { - const capacity = sessionManager.getCapacity(); - logger.warn('⛔ Connection rejected - server at capacity', { - active: capacity.active, - max: capacity.max, - }); - - return new Response( - JSON.stringify({ - error: 'Server at capacity', - capacity: capacity, - }), - { - status: 503, - headers: { - 'Content-Type': 'application/json', - 'Retry-After': '60', - }, - }, - ); - } - - // Create session ID before upgrade - const sessionId = crypto.randomUUID(); - - const success = server.upgrade(req, { - data: { - sessionId, - createdAt: Date.now(), - }, - }); - - if (success) { - return undefined; - } - - return new Response('WebSocket upgrade failed', {status: 500}); - } - - // 404 for other routes - return new Response('Not Found', {status: 404}); - }, - - /** - * WebSocket handlers - */ - websocket: { - /** - * Handle new WebSocket connection - */ - open(ws) { - const {sessionId, createdAt} = ws.data; - - try { - // Build agent config from server config - // Normalize executionDir: if not provided, use resourcesDir - const agentConfig = { - resourcesDir: config.resourcesDir, - executionDir: config.executionDir || config.resourcesDir, - mcpServerPort: config.mcpServerPort, - apiKey: config.apiKey, - baseUrl: config.baseUrl, - modelName: config.modelName, - }; - - // Create session with agent - sessionManager.createSession({id: sessionId}, agentConfig); - - // Track WebSocket connection - wsConnections.set(sessionId, ws); - - stats.connections++; - - logger.info('✅ Client connected', { - sessionId, - activeSessions: sessionManager.getMetrics().activeSessions, - }); - - // Send connection confirmation - const connectionEvent: ConnectionEvent = { - type: 'connection', - data: { - status: 'connected', - sessionId, - timestamp: createdAt, - }, - }; - - ws.send(JSON.stringify(connectionEvent)); - } catch (error) { - // Should not happen (capacity checked in fetch) - logger.error('❌ Failed to create session', { - sessionId, - error: error instanceof Error ? error.message : String(error), - }); - ws.close(1008, 'Failed to create session'); - } - }, - - /** - * Handle incoming messages from client - */ - async message(ws, message) { - const {sessionId} = ws.data; - - try { - // Check if session exists - if (!sessionManager.hasSession(sessionId)) { - sendError(ws, 'Session not found'); - ws.close(1008, 'Session not found'); - return; - } - - // Parse message - const messageStr = - typeof message === 'string' - ? message - : new TextDecoder().decode(message); - - logger.debug('📥 Message received', {sessionId, message: messageStr}); - - // Parse and validate - const parsedData = JSON.parse(messageStr); - const clientMessage = tryParseClientMessage(parsedData); - - if (!clientMessage) { - sendError(ws, 'Invalid message format'); - return; - } - - // Handle cancel message - if (clientMessage.type === 'cancel') { - logger.info('🛑 Cancel request received', {sessionId}); - - const success = sessionManager.cancelExecution(sessionId); - - // Send cancelled acknowledgment - const cancelledEvent = { - type: 'cancelled', - sessionId, - message: success - ? 'Execution cancelled' - : 'No active execution to cancel', - }; - ws.send(JSON.stringify(cancelledEvent)); - - logger.info(success ? '✅ Cancel successful' : '⚠️ Nothing to cancel', {sessionId}); - return; - } - - // Handle regular message - // Try to mark session as processing (reject if already processing) - if (!sessionManager.markProcessing(sessionId)) { - sendError( - ws, - 'Session is already processing a message. Please wait.', - ); - return; - } - - // Update stats - stats.messagesProcessed++; - - // Process the message with Claude SDK - try { - await processMessage( - ws, - clientMessage.content, - config, - sessionManager, - ); - - // Mark session as idle after successful processing - sessionManager.markIdle(sessionId); - } catch (error) { - const errorMsg = - error instanceof Error ? error.message : String(error); - - // Check for event gap timeout - if (errorMsg.includes('Event gap timeout')) { - logger.error('⏱️ Agent timeout - deleting session', { - sessionId, - timeout: config.eventGapTimeoutMs, - }); - - // Send error to client - sendError( - ws, - `⏱️ Agent timeout: No activity for ${config.eventGapTimeoutMs / 1000}s`, - ); - - // Immediately delete session and close connection (now async) - await sessionManager.deleteSession(sessionId); - wsConnections.delete(sessionId); - ws.close(1008, 'Agent timeout - no activity'); - return; - } - - // Other errors - mark idle normally - sessionManager.markIdle(sessionId); - throw error; - } - } catch (error) { - logger.error('❌ Error processing message', { - sessionId, - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, - }); - - // Mark session as idle on error - sessionManager.markIdle(sessionId); - - sendError( - ws, - 'Error processing message: ' + - (error instanceof Error ? error.message : String(error)), - ); - } - }, - - /** - * Handle WebSocket close - */ - async close(ws, code, reason) { - const {sessionId} = ws.data; - - // Delete session from manager (now async) - await sessionManager.deleteSession(sessionId); - - // Remove WebSocket tracking - wsConnections.delete(sessionId); - - logger.info('👋 Client disconnected', { - sessionId, - code, - reason: reason || 'No reason provided', - remainingSessions: sessionManager.getMetrics().activeSessions, - }); - }, - }, - }); - - logger.info(`✅ Server started on port ${config.port}`); - logger.info(` WebSocket: ws://localhost:${config.port}`); - logger.info(` Health: http://localhost:${config.port}/health`); - - return server; -} - -/** - * Process a message through ClaudeSDKAgent and stream events back - */ -async function processMessage( - ws: ServerWebSocket, - message: string, - config: ServerConfig, - sessionManager: SessionManager, -) { - const {sessionId} = ws.data; - - logger.info('🤖 Processing with agent...', {sessionId, message}); - - try { - // Get agent for this session - const agent = sessionManager.getAgent(sessionId); - if (!agent) { - throw new Error('Agent not found for session'); - } - - let eventCount = 0; - let lastEventType = ''; - let lastEventTime = Date.now(); - - // Get async iterator from agent - const iterator = agent.execute(message)[Symbol.asyncIterator](); - - // Stream events with gap timeout monitoring (SAME AS BEFORE) - while (true) { - // Calculate time since last event - const timeSinceLastEvent = Date.now() - lastEventTime; - const remainingTime = Math.max( - 1000, - config.eventGapTimeoutMs - timeSinceLastEvent, - ); - - // Create timeout promise - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => { - reject(new Error('EventGapTimeout')); - }, remainingTime); - }); - - // Race next event with gap timeout - let result; - try { - result = await Promise.race([iterator.next(), timeoutPromise]); - } catch (timeoutError) { - // Cleanup iterator (fire-and-forget - session will be deleted anyway) - if (iterator.return) { - iterator.return(undefined).catch(() => {}); - } - throw new Error( - `Event gap timeout: No activity for ${config.eventGapTimeoutMs / 1000}s`, - ); - } - - // Check if iteration is done - if (result.done) break; - - // Update last event time - lastEventTime = Date.now(); - const formattedEvent = result.value; // Already FormattedEvent! - - eventCount++; - lastEventType = formattedEvent.type; - - // Send to client - catch errors if client disconnected - try { - ws.send(JSON.stringify(formattedEvent.toJSON())); - - logger.debug('📤 Event sent', { - sessionId, - type: formattedEvent.type, - eventCount, - }); - } catch (sendError) { - // Client disconnected during streaming - logger.info( - '⚠️ Client disconnected during event streaming, stopping iterator', - { - sessionId, - eventCount, - }, - ); - - // Cleanup iterator - if (iterator.return) { - await iterator.return(undefined).catch(() => {}); - } - - // Exit cleanly - don't throw, just return - // (throwing would trigger outer error handler which tries to sendError again) - return; - } - } - - logger.info('✅ Message processed successfully', { - sessionId, - totalEvents: eventCount, - lastEventType, - }); - } catch (error) { - logger.error('❌ Agent error', { - sessionId, - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, - }); - - sendError( - ws, - 'Agent error: ' + - (error instanceof Error ? error.message : String(error)), - ); - - // Re-throw to be caught by outer handler - throw error; - } -} - -/** - * Send an error event to the client - */ -function sendError(ws: ServerWebSocket, error: string) { - const errorEvent: ErrorEvent = { - type: 'error', - error, - }; - - ws.send(JSON.stringify(errorEvent)); -} - -/** - * Handle health check endpoint - */ -function handleHealthCheck(sessionManager: SessionManager): Response { - const uptime = Date.now() - stats.startTime; - const capacity = sessionManager.getCapacity(); - const metrics = sessionManager.getMetrics(); - - const health = { - status: 'healthy', - uptime: uptime, - sessions: { - active: capacity.active, - max: capacity.max, - available: capacity.available, - idle: metrics.idleSessions, - processing: metrics.processingSessions, - }, - stats: { - totalConnections: stats.connections, - messagesProcessed: stats.messagesProcessed, - averageMessagesPerSession: metrics.averageMessageCount, - }, - timestamp: Date.now(), - }; - - return new Response(JSON.stringify(health), { - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - }, - }); -} diff --git a/packages/agent/tsconfig.json b/packages/agent/tsconfig.json index 56f24f6..8ab8365 100644 --- a/packages/agent/tsconfig.json +++ b/packages/agent/tsconfig.json @@ -8,6 +8,6 @@ "declarationMap": true }, "include": ["src/**/*"], - "exclude": ["dist/**/*", "node_modules"], + "exclude": ["dist/**/*", "node_modules", "src/**/*.backup", "src/**/*.backup/**/*", "src/*.backup/**/*", "src/agent.backup/**/*", "src/http-server.backup/**/*", "src/session.backup/**/*", "src/websocket.backup/**/*"], "references": [{"path": "../controller-server"}, {"path": "../tools"}] } diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts index bdfe0d0..65db6dc 100644 --- a/packages/server/src/main.ts +++ b/packages/server/src/main.ts @@ -8,19 +8,13 @@ import type http from 'node:http'; import fs from 'node:fs'; import path from 'node:path'; -import { - createAgentServer, - registerAgents, - type AgentServerConfig, -} from '@browseros/agent'; +import { createHttpServer as createAgentHttpServer } from '@browseros/agent'; import { ensureBrowserConnected, McpContext, Mutex, logger, readVersion, - fetchBrowserOSConfig, - getLLMConfigFromProvider, } from '@browseros/common'; import { ControllerContext, @@ -68,7 +62,7 @@ void (async () => { toolMutex, }); - const agentServer = await startAgentServer(ports, controllerBridge); + const agentServer = startAgentServer(ports); logSummary(ports); @@ -181,104 +175,30 @@ function startMcpServer(config: { return mcpServer; } -// get LLM configuration for agent server -async function getLLMConfig(): Promise<{ - apiKey?: string; - baseUrl: string; - modelName: string; -}> { - const envApiKey = process.env.BROWSEROS_API_KEY; - const envBaseUrl = process.env.BROWSEROS_LLM_BASE_URL; - const envModelName = process.env.BROWSEROS_LLM_MODEL_NAME; - - let configApiKey: string | undefined; - let configBaseUrl: string | undefined; - let configModelName: string | undefined; - - // Try to fetch from config URL - const configUrl = process.env.BROWSEROS_CONFIG_URL; - if (configUrl) { - try { - logger.info('Fetching LLM config from BrowserOS Config URL', { - configUrl, - }); - const config = await fetchBrowserOSConfig(configUrl); - const llmConfig = getLLMConfigFromProvider(config, 'default'); - - configApiKey = llmConfig.apiKey; - configBaseUrl = llmConfig.baseUrl; - configModelName = llmConfig.modelName; - - logger.info('Loaded config from BrowserOS Config (default provider)'); - } catch (error) { - logger.warn('Failed to fetch config from URL', { - error: error instanceof Error ? error.message : String(error), - }); - } - } - - // Apply env var overrides (env takes precedence) - const apiKey = envApiKey ?? configApiKey; - const baseUrl = envBaseUrl ?? configBaseUrl; - const modelName = envModelName ?? configModelName; - - // Validate required fields - if (!baseUrl || !modelName) { - throw new Error( - 'LLM configuration required: baseUrl and modelName must be set via BROWSEROS_LLM_BASE_URL and BROWSEROS_LLM_MODEL_NAME environment variables, or via BROWSEROS_CONFIG_URL', - ); - } - - logger.info('Using LLM config', { - baseUrl, - modelName, - apiKeySource: envApiKey ? 'env' : configApiKey ? 'config' : 'none', - }); - - return { - apiKey, - baseUrl, - modelName, - }; -} - -async function startAgentServer( +function startAgentServer( ports: ReturnType, - controllerBridge: ControllerBridge, -): Promise { - // Register all available agents (Codex SDK, Claude SDK, etc.) - registerAgents(); +): { server: any; config: any } { + const mcpServerUrl = `http://127.0.0.1:${ports.httpMcpPort}/mcp`; - const llmConfig = await getLLMConfig(); - - const agentConfig: AgentServerConfig = { + const { server, config } = createAgentHttpServer({ port: ports.agentPort, - resourcesDir: ports.resourcesDir, - executionDir: ports.executionDir, - mcpServerPort: ports.httpMcpPort, - apiKey: llmConfig.apiKey, - baseUrl: llmConfig.baseUrl, - modelName: llmConfig.modelName, - maxSessions: parseInt(process.env.MAX_SESSIONS || '5'), - idleTimeoutMs: parseInt(process.env.SESSION_IDLE_TIMEOUT_MS || '90000'), - eventGapTimeoutMs: parseInt(process.env.EVENT_GAP_TIMEOUT_MS || '120000'), - }; - - const agentServer = createAgentServer(agentConfig, controllerBridge); + host: '0.0.0.0', + corsOrigins: ['*'], + tempDir: ports.executionDir || ports.resourcesDir, + mcpServerUrl, + }); - logger.info(`[Agent Server] Listening on ws://127.0.0.1:${ports.agentPort}`); - logger.info( - `[Agent Server] Config: resourcesDir=${agentConfig.resourcesDir}, model=${agentConfig.modelName || 'default'}, sessions=${agentConfig.maxSessions}`, - ); + logger.info(`[Agent Server] Listening on http://127.0.0.1:${ports.agentPort}`); + logger.info(`[Agent Server] MCP Server URL: ${mcpServerUrl}`); - return agentServer; + return { server, config }; } function logSummary(ports: ReturnType) { logger.info(''); logger.info('Services running:'); logger.info(` Controller Server: ws://127.0.0.1:${ports.extensionPort}`); - logger.info(` Agent Server: ws://127.0.0.1:${ports.agentPort}`); + logger.info(` Agent Server: http://127.0.0.1:${ports.agentPort}`); if (ports.mcpServerEnabled) { logger.info(` MCP Server: http://127.0.0.1:${ports.httpMcpPort}/mcp`); } @@ -287,7 +207,7 @@ function logSummary(ports: ReturnType) { function createShutdownHandler( mcpServer: http.Server, - agentServer: any, + agentServer: { server: any; config: any }, controllerBridge: ControllerBridge, ) { return async () => { @@ -296,7 +216,7 @@ function createShutdownHandler( await shutdownMcpServer(mcpServer, logger); logger.info('Stopping agent server...'); - agentServer.stop(); + agentServer.server.close(); logger.info('Closing ControllerBridge...'); await controllerBridge.close(); From 4a19abe785b949564eff682a605c2225d9f645ea Mon Sep 17 00:00:00 2001 From: shivammittal274 Date: Thu, 27 Nov 2025 02:30:14 +0530 Subject: [PATCH 7/7] session management and http server code --- packages/agent/src/agent/GeminiAgent.ts | 2 -- .../agent/gemini-vercel-sdk-adapter/index.ts | 1 + .../strategies/response.ts | 32 +++++++++++++++++++ packages/agent/src/http/HttpServer.ts | 8 +++-- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/packages/agent/src/agent/GeminiAgent.ts b/packages/agent/src/agent/GeminiAgent.ts index fa019fa..a7ef2c4 100644 --- a/packages/agent/src/agent/GeminiAgent.ts +++ b/packages/agent/src/agent/GeminiAgent.ts @@ -94,8 +94,6 @@ export class GeminiAgent { }); await geminiConfig.initialize(); - - console.log('resolvedConfig', resolvedConfig); const contentGenerator = new VercelAIContentGenerator(resolvedConfig); (geminiConfig as unknown as { contentGenerator: VercelAIContentGenerator }).contentGenerator = contentGenerator; diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts index b2af847..35520d7 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/index.ts @@ -122,6 +122,7 @@ export class VercelAIContentGenerator implements ContentGenerator { tools, temperature: request.config?.temperature, topP: request.config?.topP, + abortSignal: request.config?.abortSignal, }); return this.responseStrategy.streamToGemini( diff --git a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts index 6f064da..3a3a452 100644 --- a/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts +++ b/packages/agent/src/agent/gemini-vercel-sdk-adapter/strategies/response.ts @@ -186,6 +186,25 @@ export class ResponseConversionStrategy { usage = this.estimateUsage(textAccumulator); } + // Emit finish stream part to Hono SSE for useChat compatibility + if (honoStream) { + try { + // Emit finish_message part with finishReason and usage + // Format: e:{"finishReason":"stop","usage":{"promptTokens":10,"completionTokens":5}} + // Map to LanguageModelV1FinishReason: 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other' | 'unknown' + const mappedFinishReason = this.mapToDataStreamFinishReason(finishReason); + await honoStream.write(formatDataStreamPart('finish_message', { + finishReason: mappedFinishReason, + usage: usage ? { + promptTokens: usage.promptTokens ?? 0, + completionTokens: usage.completionTokens ?? 0, + } : undefined, + })); + } catch { + // Failed to write finish part + } + } + // Yield final response with tool calls and metadata if (toolCallsMap.size > 0 || finishReason || usage) { const parts: Part[] = []; @@ -281,6 +300,19 @@ export class ResponseConversionStrategy { } } + /** + * Map Vercel finish reasons to data stream protocol finish reasons + * LanguageModelV1FinishReason: 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other' | 'unknown' + * Mostly passthrough except 'max-tokens' → 'length' + */ + private mapToDataStreamFinishReason( + reason: VercelFinishReason | undefined, + ): 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other' | 'unknown' { + if (!reason) return 'stop'; + if (reason === 'max-tokens') return 'length'; + return reason; + } + /** * Create empty response for error cases */ diff --git a/packages/agent/src/http/HttpServer.ts b/packages/agent/src/http/HttpServer.ts index 2f887e2..894815d 100644 --- a/packages/agent/src/http/HttpServer.ts +++ b/packages/agent/src/http/HttpServer.ts @@ -48,9 +48,10 @@ export function createHttpServer(config: HttpServerConfig) { app.use( '/*', cors({ - origin: validatedConfig.corsOrigins, + origin: (origin) => origin || '*', allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'], allowHeaders: ['Content-Type', 'Authorization'], + credentials: true, }), ); @@ -101,6 +102,9 @@ export function createHttpServer(config: HttpServerConfig) { c.header('Cache-Control', 'no-cache'); c.header('Connection', 'keep-alive'); + // Get abort signal from the raw request - fires when client disconnects + const abortSignal = c.req.raw.signal; + return stream(c, async (honoStream) => { try { const agent = await sessionManager.getOrCreate({ @@ -121,7 +125,7 @@ export function createHttpServer(config: HttpServerConfig) { mcpServerUrl, }); - await agent.execute(request.message, honoStream); + await agent.execute(request.message, honoStream, abortSignal); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Agent execution failed'; logger.error('Agent execution error', {