diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f5b577..39c0cc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,6 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: npm - - run: npm ci + - run: rm -f package-lock.json && npm install - run: npm run lint - run: npm test diff --git a/README.md b/README.md index dc118b6..b762ae6 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,12 @@ Requires Node.js >= 18. npm install @jackchen_me/open-multi-agent ``` -Set `ANTHROPIC_API_KEY` (and optionally `OPENAI_API_KEY` or `GITHUB_TOKEN` for Copilot) in your environment. Local models via Ollama require no API key — see [example 06](examples/06-local-model.ts). +Set the API key for your provider. Local models via Ollama require no API key — see [example 06](examples/06-local-model.ts). + +- `ANTHROPIC_API_KEY` +- `OPENAI_API_KEY` +- `GEMINI_API_KEY` +- `GITHUB_TOKEN` (for Copilot) Three agents, one goal — the framework handles the rest: @@ -156,6 +161,7 @@ npx tsx examples/01-single-agent.ts │ - stream() │ │ - AnthropicAdapter │ └────────┬──────────┘ │ - OpenAIAdapter │ │ │ - CopilotAdapter │ + │ │ - GeminiAdapter │ │ └──────────────────────┘ ┌────────▼──────────┐ │ AgentRunner │ ┌──────────────────────┐ @@ -183,6 +189,7 @@ npx tsx examples/01-single-agent.ts | OpenAI (GPT) | `provider: 'openai'` | `OPENAI_API_KEY` | Verified | | Grok (xAI) | `provider: 'grok'` | `XAI_API_KEY` | Verified | | GitHub Copilot | `provider: 'copilot'` | `GITHUB_TOKEN` | Verified | +| Gemini | `provider: 'gemini'` | `GEMINI_API_KEY` | Verified | | Ollama / vLLM / LM Studio | `provider: 'openai'` + `baseURL` | — | Verified | | llama.cpp server | `provider: 'openai'` + `baseURL` | — | Verified | @@ -190,6 +197,33 @@ Verified local models with tool-calling: **Gemma 4** (see [example 08](examples/ Any OpenAI-compatible API should work via `provider: 'openai'` + `baseURL` (DeepSeek, Groq, Mistral, Qwen, MiniMax, etc.). **Grok now has first-class support** via `provider: 'grok'`. +### Local Model Tool-Calling + +The framework supports tool-calling with local models served by Ollama, vLLM, LM Studio, or llama.cpp. Tool-calling is handled natively by these servers via the OpenAI-compatible API. + +**Verified models:** Gemma 4, Llama 3.1, Qwen 3, Mistral, Phi-4. See the full list at [ollama.com/search?c=tools](https://ollama.com/search?c=tools). + +**Fallback extraction:** If a local model returns tool calls as text instead of using the `tool_calls` wire format (common with thinking models or misconfigured servers), the framework automatically extracts them from the text output. + +**Timeout:** Local inference can be slow. Use `timeoutMs` on `AgentConfig` to prevent indefinite hangs: + +```typescript +const localAgent: AgentConfig = { + name: 'local', + model: 'llama3.1', + provider: 'openai', + baseURL: 'http://localhost:11434/v1', + apiKey: 'ollama', + tools: ['bash', 'file_read'], + timeoutMs: 120_000, // abort after 2 minutes +} +``` + +**Troubleshooting:** +- Model not calling tools? Ensure it appears in Ollama's [Tools category](https://ollama.com/search?c=tools). Not all models support tool-calling. +- Using Ollama? Update to the latest version (`ollama update`) — older versions have known tool-calling bugs. +- Proxy interfering? Use `no_proxy=localhost` when running against local servers. + ### LLM Configuration Examples ```typescript diff --git a/README_zh.md b/README_zh.md index a8b680c..7a4acc1 100644 --- a/README_zh.md +++ b/README_zh.md @@ -155,6 +155,7 @@ npx tsx examples/01-single-agent.ts │ - stream() │ │ - AnthropicAdapter │ └────────┬──────────┘ │ - OpenAIAdapter │ │ │ - CopilotAdapter │ + │ │ - GeminiAdapter │ │ └──────────────────────┘ ┌────────▼──────────┐ │ AgentRunner │ ┌──────────────────────┐ @@ -181,6 +182,7 @@ npx tsx examples/01-single-agent.ts | Anthropic (Claude) | `provider: 'anthropic'` | `ANTHROPIC_API_KEY` | 已验证 | | OpenAI (GPT) | `provider: 'openai'` | `OPENAI_API_KEY` | 已验证 | | GitHub Copilot | `provider: 'copilot'` | `GITHUB_TOKEN` | 已验证 | +| Gemini | `provider: 'gemini'` | `GEMINI_API_KEY` | 已验证 | | Ollama / vLLM / LM Studio | `provider: 'openai'` + `baseURL` | — | 已验证 | 已验证支持 tool-calling 的本地模型:**Gemma 4**(见[示例 08](examples/08-gemma4-local.ts))。 diff --git a/examples/06-local-model.ts b/examples/06-local-model.ts index d7cf292..977950b 100644 --- a/examples/06-local-model.ts +++ b/examples/06-local-model.ts @@ -64,6 +64,7 @@ Your review MUST include these sections: Be specific and constructive. Reference line numbers or function names when possible.`, tools: ['file_read'], maxTurns: 4, + timeoutMs: 120_000, // 2 min — local models can be slow } // --------------------------------------------------------------------------- diff --git a/examples/13-gemini.ts b/examples/13-gemini.ts new file mode 100644 index 0000000..ddbf0c1 --- /dev/null +++ b/examples/13-gemini.ts @@ -0,0 +1,48 @@ +/** + * Quick smoke test for the Gemini adapter. + * + * Run: + * npx tsx examples/13-gemini.ts + * + * If GEMINI_API_KEY is not set, the adapter will not work. + */ + +import { OpenMultiAgent } from '../src/index.js' +import type { OrchestratorEvent } from '../src/types.js' + +const orchestrator = new OpenMultiAgent({ + defaultModel: 'gemini-2.5-flash', + defaultProvider: 'gemini', + onProgress: (event: OrchestratorEvent) => { + if (event.type === 'agent_start') { + console.log(`[start] agent=${event.agent}`) + } else if (event.type === 'agent_complete') { + console.log(`[complete] agent=${event.agent}`) + } + }, +}) + +console.log('Testing Gemini adapter with gemini-2.5-flash...\n') + +const result = await orchestrator.runAgent( + { + name: 'assistant', + model: 'gemini-2.5-flash', + provider: 'gemini', + systemPrompt: 'You are a helpful assistant. Keep answers brief.', + maxTurns: 1, + maxTokens: 256, + }, + 'What is 2 + 2? Reply in one sentence.', +) + +if (result.success) { + console.log('\nAgent output:') + console.log('─'.repeat(60)) + console.log(result.output) + console.log('─'.repeat(60)) + console.log(`\nTokens: input=${result.tokenUsage.input_tokens}, output=${result.tokenUsage.output_tokens}`) +} else { + console.error('Agent failed:', result.output) + process.exit(1) +} diff --git a/package-lock.json b/package-lock.json index 0b541e2..55afff8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jackchen_me/open-multi-agent", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackchen_me/open-multi-agent", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.52.0", @@ -14,6 +14,7 @@ "zod": "^3.23.0" }, "devDependencies": { + "@google/genai": "^1.48.0", "@types/node": "^22.0.0", "tsx": "^4.21.0", "typescript": "^5.6.0", @@ -21,6 +22,14 @@ }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "@google/genai": "^1.48.0" + }, + "peerDependenciesMeta": { + "@google/genai": { + "optional": true + } } }, "node_modules/@anthropic-ai/sdk": { @@ -33,9 +42,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -46,13 +55,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -63,13 +72,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -80,13 +89,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -97,7 +106,7 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { @@ -118,9 +127,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -131,13 +140,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -148,13 +157,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -165,13 +174,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -182,13 +191,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -199,13 +208,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -216,13 +225,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -233,13 +242,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -250,13 +259,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -267,13 +276,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -284,13 +293,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -301,13 +310,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -318,7 +327,7 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-arm64": { @@ -339,9 +348,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -352,7 +361,7 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { @@ -373,9 +382,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -386,7 +395,7 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/openharmony-arm64": { @@ -407,9 +416,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -420,13 +429,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -437,13 +446,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -454,13 +463,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -471,7 +480,31 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.48.0.tgz", + "integrity": "sha512-plonYK4ML2PrxsRD9SeqmFt76eREWkQdPCglOA6aYDzL1AAbE+7PUnT54SvpWGfws13L0AZEqGSpL7+1IPnTxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -481,33 +514,79 @@ "dev": true, "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", - "cpu": [ - "arm" - ], + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "license": "BSD-3-Clause" }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", - "cpu": [ - "arm64" - ], + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.60.1", @@ -523,314 +602,6 @@ "darwin" ] }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", @@ -857,6 +628,13 @@ "form-data": "^4.0.4" } }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitest/expect": { "version": "2.1.9", "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-2.1.9.tgz", @@ -982,6 +760,16 @@ "node": ">=6.5" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/agentkeepalive": { "version": "4.6.0", "resolved": "https://registry.npmmirror.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz", @@ -1010,6 +798,44 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", @@ -1072,6 +898,16 @@ "node": ">= 0.8" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", @@ -1123,6 +959,16 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1214,6 +1060,380 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", @@ -1243,6 +1463,47 @@ "node": ">=12.0.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fetch-blob/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", @@ -1278,6 +1539,19 @@ "node": ">= 12.20" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", @@ -1302,6 +1576,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1352,6 +1675,34 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", @@ -1403,6 +1754,20 @@ "node": ">= 0.4" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -1412,6 +1777,46 @@ "ms": "^2.0.0" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/loupe": { "version": "3.2.1", "resolved": "https://registry.npmmirror.com/loupe/-/loupe-3.2.1.tgz", @@ -1569,6 +1974,20 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", @@ -1622,6 +2041,31 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -1632,6 +2076,16 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/rollup": { "version": "4.60.1", "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.1.tgz", @@ -1677,6 +2131,27 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", @@ -1778,74 +2253,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", @@ -1863,312 +2270,6 @@ "node": ">=18" } }, - "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/tsx/node_modules/esbuild": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", @@ -2422,6 +2523,28 @@ "node": ">=8" } }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", diff --git a/package.json b/package.json index fc54d44..94d8a7c 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,16 @@ "openai": "^4.73.0", "zod": "^3.23.0" }, + "peerDependencies": { + "@google/genai": "^1.48.0" + }, + "peerDependenciesMeta": { + "@google/genai": { + "optional": true + } + }, "devDependencies": { + "@google/genai": "^1.48.0", "@types/node": "^22.0.0", "tsx": "^4.21.0", "typescript": "^5.6.0", diff --git a/src/agent/agent.ts b/src/agent/agent.ts index 0cc3c8b..904b5bc 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -50,6 +50,19 @@ import { const ZERO_USAGE: TokenUsage = { input_tokens: 0, output_tokens: 0 } +/** + * Combine two {@link AbortSignal}s so that aborting either one cancels the + * returned signal. Works on Node 18+ (no `AbortSignal.any` required). + */ +function mergeAbortSignals(a: AbortSignal, b: AbortSignal): AbortSignal { + const controller = new AbortController() + if (a.aborted || b.aborted) { controller.abort(); return controller.signal } + const abort = () => controller.abort() + a.addEventListener('abort', abort, { once: true }) + b.addEventListener('abort', abort, { once: true }) + return controller.signal +} + function addUsage(a: TokenUsage, b: TokenUsage): TokenUsage { return { input_tokens: a.input_tokens + b.input_tokens, @@ -294,10 +307,22 @@ export class Agent { } // Auto-generate runId when onTrace is provided but runId is missing const needsRunId = callerOptions?.onTrace && !callerOptions.runId + // Create a fresh timeout signal per run (not per runner) so that + // each run() / prompt() call gets its own timeout window. + const timeoutSignal = this.config.timeoutMs !== undefined && this.config.timeoutMs > 0 + ? AbortSignal.timeout(this.config.timeoutMs) + : undefined + // Merge caller-provided abortSignal with the timeout signal so that + // either cancellation source is respected. + const callerAbort = callerOptions?.abortSignal + const effectiveAbort = timeoutSignal && callerAbort + ? mergeAbortSignals(timeoutSignal, callerAbort) + : timeoutSignal ?? callerAbort const runOptions: RunOptions = { ...callerOptions, onMessage: internalOnMessage, ...(needsRunId ? { runId: generateRunId() } : undefined), + ...(effectiveAbort ? { abortSignal: effectiveAbort } : undefined), } const result = await runner.run(messages, runOptions) @@ -467,8 +492,12 @@ export class Agent { } const runner = await this.getRunner() + // Fresh timeout per stream call, same as executeRun. + const timeoutSignal = this.config.timeoutMs !== undefined && this.config.timeoutMs > 0 + ? AbortSignal.timeout(this.config.timeoutMs) + : undefined - for await (const event of runner.stream(messages)) { + for await (const event of runner.stream(messages, timeoutSignal ? { abortSignal: timeoutSignal } : {})) { if (event.type === 'done') { const result = event.data as import('./runner.js').RunResult this.state.tokenUsage = addUsage(this.state.tokenUsage, result.tokenUsage) diff --git a/src/agent/runner.ts b/src/agent/runner.ts index 456e4c0..f7f1d6a 100644 --- a/src/agent/runner.ts +++ b/src/agent/runner.ts @@ -83,6 +83,11 @@ export interface RunOptions { readonly onToolResult?: (name: string, result: ToolResult) => void /** Fired after each complete {@link LLMMessage} is appended. */ readonly onMessage?: (message: LLMMessage) => void + /** + * Fired when the runner detects a potential configuration issue. + * For example, when a model appears to ignore tool definitions. + */ + readonly onWarning?: (message: string) => void /** Trace callback for observability spans. Async callbacks are safe. */ readonly onTrace?: (event: TraceEvent) => void | Promise /** Run ID for trace correlation. */ @@ -92,10 +97,10 @@ export interface RunOptions { /** Agent name for trace correlation (overrides RunnerOptions.agentName). */ readonly traceAgent?: string /** - * Fired when the runner detects a potential issue (e.g. loop detection, - * model ignoring tool definitions). + * Per-call abort signal. When set, takes precedence over the static + * {@link RunnerOptions.abortSignal}. Useful for per-run timeouts. */ - readonly onWarning?: (message: string) => void + readonly abortSignal?: AbortSignal } /** The aggregated result returned when a full run completes. */ @@ -236,13 +241,16 @@ export class AgentRunner { ? allDefs.filter(d => this.options.allowedTools!.includes(d.name)) : allDefs + // Per-call abortSignal takes precedence over the static one. + const effectiveAbortSignal = options.abortSignal ?? this.options.abortSignal + const baseChatOptions: LLMChatOptions = { model: this.options.model, tools: toolDefs.length > 0 ? toolDefs : undefined, maxTokens: this.options.maxTokens, temperature: this.options.temperature, systemPrompt: this.options.systemPrompt, - abortSignal: this.options.abortSignal, + abortSignal: effectiveAbortSignal, } // Loop detection state — only allocated when configured. @@ -259,7 +267,7 @@ export class AgentRunner { // ----------------------------------------------------------------- while (true) { // Respect abort before each LLM call. - if (this.options.abortSignal?.aborted) { + if (effectiveAbortSignal?.aborted) { break } @@ -361,6 +369,15 @@ export class AgentRunner { // Step 3: Decide whether to continue looping. // ------------------------------------------------------------------ if (toolUseBlocks.length === 0) { + // Warn on first turn if tools were provided but model didn't use them. + if (turns === 1 && toolDefs.length > 0 && options.onWarning) { + const agentName = this.options.agentName ?? 'unknown' + options.onWarning( + `Agent "${agentName}" has ${toolDefs.length} tool(s) available but the model ` + + `returned no tool calls. If using a local model, verify it supports tool calling ` + + `(see https://ollama.com/search?c=tools).`, + ) + } // No tools requested — this is the terminal assistant turn. finalOutput = turnText break diff --git a/src/llm/adapter.ts b/src/llm/adapter.ts index 98d4907..dc4fe82 100644 --- a/src/llm/adapter.ts +++ b/src/llm/adapter.ts @@ -11,6 +11,7 @@ * * const anthropic = createAdapter('anthropic') * const openai = createAdapter('openai', process.env.OPENAI_API_KEY) + * const gemini = createAdapter('gemini', process.env.GEMINI_API_KEY) * ``` */ @@ -37,7 +38,7 @@ import type { LLMAdapter } from '../types.js' * Additional providers can be integrated by implementing {@link LLMAdapter} * directly and bypassing this factory. */ -export type SupportedProvider = 'anthropic' | 'copilot' | 'grok' | 'openai' +export type SupportedProvider = 'anthropic' | 'copilot' | 'grok' | 'openai' | 'gemini' /** * Instantiate the appropriate {@link LLMAdapter} for the given provider. @@ -46,6 +47,7 @@ export type SupportedProvider = 'anthropic' | 'copilot' | 'grok' | 'openai' * explicitly: * - `anthropic` → `ANTHROPIC_API_KEY` * - `openai` → `OPENAI_API_KEY` + * - `gemini` → `GEMINI_API_KEY` / `GOOGLE_API_KEY` * - `grok` → `XAI_API_KEY` * - `copilot` → `GITHUB_COPILOT_TOKEN` / `GITHUB_TOKEN`, or interactive * OAuth2 device flow if neither is set @@ -75,6 +77,10 @@ export async function createAdapter( const { CopilotAdapter } = await import('./copilot.js') return new CopilotAdapter(apiKey) } + case 'gemini': { + const { GeminiAdapter } = await import('./gemini.js') + return new GeminiAdapter(apiKey) + } case 'openai': { const { OpenAIAdapter } = await import('./openai.js') return new OpenAIAdapter(apiKey, baseURL) diff --git a/src/llm/copilot.ts b/src/llm/copilot.ts index 7e829fe..44349f8 100644 --- a/src/llm/copilot.ts +++ b/src/llm/copilot.ts @@ -313,7 +313,8 @@ export class CopilotAdapter implements LLMAdapter { }, ) - return fromOpenAICompletion(completion) + const toolNames = options.tools?.map(t => t.name) + return fromOpenAICompletion(completion, toolNames) } // ------------------------------------------------------------------------- diff --git a/src/llm/gemini.ts b/src/llm/gemini.ts new file mode 100644 index 0000000..f68d981 --- /dev/null +++ b/src/llm/gemini.ts @@ -0,0 +1,378 @@ +/** + * @fileoverview Google Gemini adapter implementing {@link LLMAdapter}. + * + * Built for `@google/genai` (the unified Google Gen AI SDK, v1.x), NOT the + * legacy `@google/generative-ai` package. + * + * Converts between the framework's internal {@link ContentBlock} types and the + * `@google/genai` SDK's wire format, handling tool definitions, system prompts, + * and both batch and streaming response paths. + * + * API key resolution order: + * 1. `apiKey` constructor argument + * 2. `GEMINI_API_KEY` environment variable + * 3. `GOOGLE_API_KEY` environment variable + * + * @example + * ```ts + * import { GeminiAdapter } from './gemini.js' + * + * const adapter = new GeminiAdapter() + * const response = await adapter.chat(messages, { + * model: 'gemini-2.5-flash', + * maxTokens: 1024, + * }) + * ``` + */ + +import { + GoogleGenAI, + FunctionCallingConfigMode, + type Content, + type FunctionDeclaration, + type GenerateContentConfig, + type GenerateContentResponse, + type Part, + type Tool as GeminiTool, +} from '@google/genai' + +import type { + ContentBlock, + LLMAdapter, + LLMChatOptions, + LLMMessage, + LLMResponse, + LLMStreamOptions, + LLMToolDef, + StreamEvent, + ToolUseBlock, +} from '../types.js' + +// --------------------------------------------------------------------------- +// Internal helpers +// --------------------------------------------------------------------------- + +/** + * Map framework role names to Gemini role names. + * + * Gemini uses `"model"` instead of `"assistant"`. + */ +function toGeminiRole(role: 'user' | 'assistant'): string { + return role === 'assistant' ? 'model' : 'user' +} + +/** + * Convert framework messages into Gemini's {@link Content}[] format. + * + * Key differences from Anthropic: + * - Gemini uses `"model"` instead of `"assistant"`. + * - `functionResponse` parts (tool results) must appear in `"user"` turns. + * - `functionCall` parts appear in `"model"` turns. + * - We build a name lookup map from tool_use blocks so tool_result blocks + * can resolve the function name required by Gemini's `functionResponse`. + */ +function toGeminiContents(messages: LLMMessage[]): Content[] { + // First pass: build id → name map for resolving tool results. + const toolNameById = new Map() + for (const msg of messages) { + for (const block of msg.content) { + if (block.type === 'tool_use') { + toolNameById.set(block.id, block.name) + } + } + } + + return messages.map((msg): Content => { + const parts: Part[] = msg.content.map((block): Part => { + switch (block.type) { + case 'text': + return { text: block.text } + + case 'tool_use': + return { + functionCall: { + id: block.id, + name: block.name, + args: block.input, + }, + } + + case 'tool_result': { + const name = toolNameById.get(block.tool_use_id) ?? block.tool_use_id + return { + functionResponse: { + id: block.tool_use_id, + name, + response: { + content: + typeof block.content === 'string' + ? block.content + : JSON.stringify(block.content), + isError: block.is_error ?? false, + }, + }, + } + } + + case 'image': + return { + inlineData: { + mimeType: block.source.media_type, + data: block.source.data, + }, + } + + default: { + const _exhaustive: never = block + throw new Error(`Unhandled content block type: ${JSON.stringify(_exhaustive)}`) + } + } + }) + + return { role: toGeminiRole(msg.role), parts } + }) +} + +/** + * Convert framework {@link LLMToolDef}s into a Gemini `tools` config array. + * + * In `@google/genai`, function declarations use `parametersJsonSchema` (not + * `parameters` or `input_schema`). All declarations are grouped under a single + * tool entry. + */ +function toGeminiTools(tools: readonly LLMToolDef[]): GeminiTool[] { + const functionDeclarations: FunctionDeclaration[] = tools.map((t) => ({ + name: t.name, + description: t.description, + parametersJsonSchema: t.inputSchema as Record, + })) + return [{ functionDeclarations }] +} + +/** + * Build the {@link GenerateContentConfig} shared by chat() and stream(). + */ +function buildConfig( + options: LLMChatOptions | LLMStreamOptions, +): GenerateContentConfig { + return { + maxOutputTokens: options.maxTokens ?? 4096, + temperature: options.temperature, + systemInstruction: options.systemPrompt, + tools: options.tools ? toGeminiTools(options.tools) : undefined, + toolConfig: options.tools + ? { functionCallingConfig: { mode: FunctionCallingConfigMode.AUTO } } + : undefined, + } +} + +/** + * Generate a stable pseudo-random ID string for tool use blocks. + * + * Gemini may not always return call IDs (especially in streaming), so we + * fabricate them when absent to satisfy the framework's {@link ToolUseBlock} + * contract. + */ +function generateId(): string { + return `gemini-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` +} + +/** + * Extract the function call ID from a Gemini part, or generate one. + * + * The `id` field exists in newer API versions but may be absent in older + * responses, so we cast conservatively and fall back to a generated ID. + */ +function getFunctionCallId(part: Part): string { + return (part.functionCall as { id?: string } | undefined)?.id ?? generateId() +} + +/** + * Convert a Gemini {@link GenerateContentResponse} into a framework + * {@link LLMResponse}. + */ +function fromGeminiResponse( + response: GenerateContentResponse, + id: string, + model: string, +): LLMResponse { + const candidate = response.candidates?.[0] + const content: ContentBlock[] = [] + + for (const part of candidate?.content?.parts ?? []) { + if (part.text !== undefined && part.text !== '') { + content.push({ type: 'text', text: part.text }) + } else if (part.functionCall !== undefined) { + content.push({ + type: 'tool_use', + id: getFunctionCallId(part), + name: part.functionCall.name ?? '', + input: (part.functionCall.args ?? {}) as Record, + }) + } + // inlineData echoes and other part types are silently ignored. + } + + // Map Gemini finish reasons to framework stop_reason vocabulary. + const finishReason = candidate?.finishReason as string | undefined + let stop_reason: LLMResponse['stop_reason'] = 'end_turn' + if (finishReason === 'MAX_TOKENS') { + stop_reason = 'max_tokens' + } else if (content.some((b) => b.type === 'tool_use')) { + // Gemini may report STOP even when it returned function calls. + stop_reason = 'tool_use' + } + + const usage = response.usageMetadata + return { + id, + content, + model, + stop_reason, + usage: { + input_tokens: usage?.promptTokenCount ?? 0, + output_tokens: usage?.candidatesTokenCount ?? 0, + }, + } +} + +// --------------------------------------------------------------------------- +// Adapter implementation +// --------------------------------------------------------------------------- + +/** + * LLM adapter backed by the Google Gemini API via `@google/genai`. + * + * Thread-safe — a single instance may be shared across concurrent agent runs. + * The underlying SDK client is stateless across requests. + */ +export class GeminiAdapter implements LLMAdapter { + readonly name = 'gemini' + + readonly #client: GoogleGenAI + + constructor(apiKey?: string) { + this.#client = new GoogleGenAI({ + apiKey: apiKey ?? process.env['GEMINI_API_KEY'] ?? process.env['GOOGLE_API_KEY'], + }) + } + + // ------------------------------------------------------------------------- + // chat() + // ------------------------------------------------------------------------- + + /** + * Send a synchronous (non-streaming) chat request and return the complete + * {@link LLMResponse}. + * + * Uses `ai.models.generateContent()` with the full conversation as `contents`, + * which is the idiomatic pattern for `@google/genai`. + */ + async chat(messages: LLMMessage[], options: LLMChatOptions): Promise { + const id = generateId() + const contents = toGeminiContents(messages) + + const response = await this.#client.models.generateContent({ + model: options.model, + contents, + config: buildConfig(options), + }) + + return fromGeminiResponse(response, id, options.model) + } + + // ------------------------------------------------------------------------- + // stream() + // ------------------------------------------------------------------------- + + /** + * Send a streaming chat request and yield {@link StreamEvent}s as they + * arrive from the API. + * + * Uses `ai.models.generateContentStream()` which returns an + * `AsyncGenerator`. Each yielded chunk has the same + * shape as a full response but contains only the delta for that chunk. + * + * Because `@google/genai` doesn't expose a `finalMessage()` helper like the + * Anthropic SDK, we accumulate content and token counts as we stream so that + * the terminal `done` event carries a complete and accurate {@link LLMResponse}. + * + * Sequence guarantees (matching the Anthropic adapter): + * - Zero or more `text` events with incremental deltas + * - Zero or more `tool_use` events (one per call; Gemini doesn't stream args) + * - Exactly one terminal event: `done` or `error` + */ + async *stream( + messages: LLMMessage[], + options: LLMStreamOptions, + ): AsyncIterable { + const id = generateId() + const contents = toGeminiContents(messages) + + try { + const streamResponse = await this.#client.models.generateContentStream({ + model: options.model, + contents, + config: buildConfig(options), + }) + + // Accumulators for building the done payload. + const accumulatedContent: ContentBlock[] = [] + let inputTokens = 0 + let outputTokens = 0 + let lastFinishReason: string | undefined + + for await (const chunk of streamResponse) { + const candidate = chunk.candidates?.[0] + + // Accumulate token counts — the API emits these on the final chunk. + if (chunk.usageMetadata) { + inputTokens = chunk.usageMetadata.promptTokenCount ?? inputTokens + outputTokens = chunk.usageMetadata.candidatesTokenCount ?? outputTokens + } + if (candidate?.finishReason) { + lastFinishReason = candidate.finishReason as string + } + + for (const part of candidate?.content?.parts ?? []) { + if (part.text) { + accumulatedContent.push({ type: 'text', text: part.text }) + yield { type: 'text', data: part.text } satisfies StreamEvent + } else if (part.functionCall) { + const toolId = getFunctionCallId(part) + const toolUseBlock: ToolUseBlock = { + type: 'tool_use', + id: toolId, + name: part.functionCall.name ?? '', + input: (part.functionCall.args ?? {}) as Record, + } + accumulatedContent.push(toolUseBlock) + yield { type: 'tool_use', data: toolUseBlock } satisfies StreamEvent + } + } + } + + // Determine stop_reason from the accumulated response. + const hasToolUse = accumulatedContent.some((b) => b.type === 'tool_use') + let stop_reason: LLMResponse['stop_reason'] = 'end_turn' + if (lastFinishReason === 'MAX_TOKENS') { + stop_reason = 'max_tokens' + } else if (hasToolUse) { + stop_reason = 'tool_use' + } + + const finalResponse: LLMResponse = { + id, + content: accumulatedContent, + model: options.model, + stop_reason, + usage: { input_tokens: inputTokens, output_tokens: outputTokens }, + } + + yield { type: 'done', data: finalResponse } satisfies StreamEvent + } catch (err) { + const error = err instanceof Error ? err : new Error(String(err)) + yield { type: 'error', data: error } satisfies StreamEvent + } + } +} diff --git a/src/llm/openai-common.ts b/src/llm/openai-common.ts index 46fc67a..cdb16a0 100644 --- a/src/llm/openai-common.ts +++ b/src/llm/openai-common.ts @@ -25,6 +25,7 @@ import type { TextBlock, ToolUseBlock, } from '../types.js' +import { extractToolCallsFromText } from '../tool/text-tool-extractor.js' // --------------------------------------------------------------------------- // Framework → OpenAI @@ -166,8 +167,18 @@ function toOpenAIAssistantMessage(msg: LLMMessage): ChatCompletionAssistantMessa * * Takes only the first choice (index 0), consistent with how the framework * is designed for single-output agents. + * + * @param completion - The raw OpenAI completion. + * @param knownToolNames - Optional whitelist of tool names. When the model + * returns no `tool_calls` but the text contains JSON + * that looks like a tool call, the fallback extractor + * uses this list to validate matches. Pass the names + * of tools sent in the request for best results. */ -export function fromOpenAICompletion(completion: ChatCompletion): LLMResponse { +export function fromOpenAICompletion( + completion: ChatCompletion, + knownToolNames?: string[], +): LLMResponse { const choice = completion.choices[0] if (choice === undefined) { throw new Error('OpenAI returned a completion with no choices') @@ -201,7 +212,35 @@ export function fromOpenAICompletion(completion: ChatCompletion): LLMResponse { content.push(toolUseBlock) } - const stopReason = normalizeFinishReason(choice.finish_reason ?? 'stop') + // --------------------------------------------------------------------------- + // Fallback: extract tool calls from text when native tool_calls is empty. + // + // Some local models (Ollama thinking models, misconfigured vLLM) return tool + // calls as plain text instead of using the tool_calls wire format. When we + // have text but no tool_calls, try to extract them from the text. + // --------------------------------------------------------------------------- + const hasNativeToolCalls = (message.tool_calls ?? []).length > 0 + if ( + !hasNativeToolCalls && + knownToolNames !== undefined && + knownToolNames.length > 0 && + message.content !== null && + message.content !== undefined && + message.content.length > 0 + ) { + const extracted = extractToolCallsFromText(message.content, knownToolNames) + if (extracted.length > 0) { + content.push(...extracted) + } + } + + const hasToolUseBlocks = content.some(b => b.type === 'tool_use') + const rawStopReason = choice.finish_reason ?? 'stop' + // If we extracted tool calls from text but the finish_reason was 'stop', + // correct it to 'tool_use' so the agent runner continues the loop. + const stopReason = hasToolUseBlocks && rawStopReason === 'stop' + ? 'tool_use' + : normalizeFinishReason(rawStopReason) return { id: completion.id, diff --git a/src/llm/openai.ts b/src/llm/openai.ts index e3f166f..cd48086 100644 --- a/src/llm/openai.ts +++ b/src/llm/openai.ts @@ -54,6 +54,7 @@ import { normalizeFinishReason, buildOpenAIMessageList, } from './openai-common.js' +import { extractToolCallsFromText } from '../tool/text-tool-extractor.js' // --------------------------------------------------------------------------- // Adapter implementation @@ -104,7 +105,8 @@ export class OpenAIAdapter implements LLMAdapter { }, ) - return fromOpenAICompletion(completion) + const toolNames = options.tools?.map(t => t.name) + return fromOpenAICompletion(completion, toolNames) } // ------------------------------------------------------------------------- @@ -241,11 +243,29 @@ export class OpenAIAdapter implements LLMAdapter { } doneContent.push(...finalToolUseBlocks) + // Fallback: extract tool calls from text when streaming produced no + // native tool_calls (same logic as fromOpenAICompletion). + if (finalToolUseBlocks.length === 0 && fullText.length > 0 && options.tools) { + const toolNames = options.tools.map(t => t.name) + const extracted = extractToolCallsFromText(fullText, toolNames) + if (extracted.length > 0) { + doneContent.push(...extracted) + for (const block of extracted) { + yield { type: 'tool_use', data: block } satisfies StreamEvent + } + } + } + + const hasToolUseBlocks = doneContent.some(b => b.type === 'tool_use') + const resolvedStopReason = hasToolUseBlocks && finalFinishReason === 'stop' + ? 'tool_use' + : normalizeFinishReason(finalFinishReason) + const finalResponse: LLMResponse = { id: completionId, content: doneContent, model: completionModel, - stop_reason: normalizeFinishReason(finalFinishReason), + stop_reason: resolvedStopReason, usage: { input_tokens: inputTokens, output_tokens: outputTokens }, } diff --git a/src/tool/text-tool-extractor.ts b/src/tool/text-tool-extractor.ts new file mode 100644 index 0000000..8c64d1d --- /dev/null +++ b/src/tool/text-tool-extractor.ts @@ -0,0 +1,219 @@ +/** + * @fileoverview Fallback tool-call extractor for local models. + * + * When a local model (Ollama, vLLM, LM Studio) returns tool calls as plain + * text instead of using the OpenAI `tool_calls` wire format, this module + * attempts to extract them from the text output. + * + * Common scenarios: + * - Ollama thinking-model bug: tool call JSON ends up inside unclosed `` tags + * - Model outputs raw JSON tool calls without the server parsing them + * - Model wraps tool calls in markdown code fences + * - Hermes-format `` tags + * + * This is a **safety net**, not the primary path. Native `tool_calls` from + * the server are always preferred. + */ + +import type { ToolUseBlock } from '../types.js' + +// --------------------------------------------------------------------------- +// ID generation +// --------------------------------------------------------------------------- + +let callCounter = 0 + +/** Generate a unique tool-call ID for extracted calls. */ +function generateToolCallId(): string { + return `extracted_call_${Date.now()}_${++callCounter}` +} + +// --------------------------------------------------------------------------- +// Internal parsers +// --------------------------------------------------------------------------- + +/** + * Try to parse a single JSON object as a tool call. + * + * Accepted shapes: + * ```json + * { "name": "bash", "arguments": { "command": "ls" } } + * { "name": "bash", "parameters": { "command": "ls" } } + * { "function": { "name": "bash", "arguments": { "command": "ls" } } } + * ``` + */ +function parseToolCallJSON( + json: unknown, + knownToolNames: ReadonlySet, +): ToolUseBlock | null { + if (json === null || typeof json !== 'object' || Array.isArray(json)) { + return null + } + + const obj = json as Record + + // Shape: { function: { name, arguments } } + if (typeof obj['function'] === 'object' && obj['function'] !== null) { + const fn = obj['function'] as Record + return parseFlat(fn, knownToolNames) + } + + // Shape: { name, arguments|parameters } + return parseFlat(obj, knownToolNames) +} + +function parseFlat( + obj: Record, + knownToolNames: ReadonlySet, +): ToolUseBlock | null { + const name = obj['name'] + if (typeof name !== 'string' || name.length === 0) return null + + // Whitelist check — don't treat arbitrary JSON as a tool call + if (knownToolNames.size > 0 && !knownToolNames.has(name)) return null + + let input: Record = {} + const args = obj['arguments'] ?? obj['parameters'] ?? obj['input'] + if (args !== null && args !== undefined) { + if (typeof args === 'string') { + try { + const parsed = JSON.parse(args) + if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { + input = parsed as Record + } + } catch { + // Malformed — use empty input + } + } else if (typeof args === 'object' && !Array.isArray(args)) { + input = args as Record + } + } + + return { + type: 'tool_use', + id: generateToolCallId(), + name, + input, + } +} + +// --------------------------------------------------------------------------- +// JSON extraction from text +// --------------------------------------------------------------------------- + +/** + * Find all top-level JSON objects in a string by tracking brace depth. + * Returns the parsed objects (not sub-objects). + */ +function extractJSONObjects(text: string): unknown[] { + const results: unknown[] = [] + let depth = 0 + let start = -1 + let inString = false + let escape = false + + for (let i = 0; i < text.length; i++) { + const ch = text[i]! + + if (escape) { + escape = false + continue + } + + if (ch === '\\' && inString) { + escape = true + continue + } + + if (ch === '"') { + inString = !inString + continue + } + + if (inString) continue + + if (ch === '{') { + if (depth === 0) start = i + depth++ + } else if (ch === '}') { + depth-- + if (depth === 0 && start !== -1) { + const candidate = text.slice(start, i + 1) + try { + results.push(JSON.parse(candidate)) + } catch { + // Not valid JSON — skip + } + start = -1 + } + } + } + + return results +} + +// --------------------------------------------------------------------------- +// Hermes format: ... +// --------------------------------------------------------------------------- + +function extractHermesToolCalls( + text: string, + knownToolNames: ReadonlySet, +): ToolUseBlock[] { + const results: ToolUseBlock[] = [] + + for (const match of text.matchAll(/\s*([\s\S]*?)\s*<\/tool_call>/g)) { + const inner = match[1]!.trim() + try { + const parsed: unknown = JSON.parse(inner) + const block = parseToolCallJSON(parsed, knownToolNames) + if (block !== null) results.push(block) + } catch { + // Malformed hermes content — skip + } + } + + return results +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +/** + * Attempt to extract tool calls from a model's text output. + * + * Tries multiple strategies in order: + * 1. Hermes `` tags + * 2. JSON objects in text (bare or inside code fences) + * + * @param text - The model's text output. + * @param knownToolNames - Whitelist of registered tool names. When non-empty, + * only JSON objects whose `name` matches a known tool + * are treated as tool calls. + * @returns Extracted {@link ToolUseBlock}s, or an empty array if none found. + */ +export function extractToolCallsFromText( + text: string, + knownToolNames: string[], +): ToolUseBlock[] { + if (text.length === 0) return [] + + const nameSet = new Set(knownToolNames) + + // Strategy 1: Hermes format + const hermesResults = extractHermesToolCalls(text, nameSet) + if (hermesResults.length > 0) return hermesResults + + // Strategy 2: Strip code fences, then extract JSON objects + const stripped = text.replace(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/g, '$1') + const jsonObjects = extractJSONObjects(stripped) + + const results: ToolUseBlock[] = [] + for (const obj of jsonObjects) { + const block = parseToolCallJSON(obj, nameSet) + if (block !== null) results.push(block) + } + + return results +} diff --git a/src/types.ts b/src/types.ts index 9fd07cc..3ffbfee 100644 --- a/src/types.ts +++ b/src/types.ts @@ -194,7 +194,7 @@ export interface BeforeRunHookContext { export interface AgentConfig { readonly name: string readonly model: string - readonly provider?: 'anthropic' | 'copilot' | 'grok' | 'openai' + readonly provider?: 'anthropic' | 'copilot' | 'grok' | 'openai' | 'gemini' /** * Custom base URL for OpenAI-compatible APIs (Ollama, vLLM, LM Studio, etc.). * Note: local servers that don't require auth still need `apiKey` set to a @@ -209,6 +209,12 @@ export interface AgentConfig { readonly maxTurns?: number readonly maxTokens?: number readonly temperature?: number + /** + * Maximum wall-clock time (in milliseconds) for the entire agent run. + * When exceeded, the run is aborted via `AbortSignal.timeout()`. + * Useful for local models where inference can be unpredictably slow. + */ + readonly timeoutMs?: number /** * Loop detection configuration. When set, the agent tracks repeated tool * calls and text outputs to detect stuck loops before `maxTurns` is reached. @@ -380,7 +386,7 @@ export interface OrchestratorEvent { export interface OrchestratorConfig { readonly maxConcurrency?: number readonly defaultModel?: string - readonly defaultProvider?: 'anthropic' | 'copilot' | 'grok' | 'openai' + readonly defaultProvider?: 'anthropic' | 'copilot' | 'grok' | 'openai' | 'gemini' readonly defaultBaseURL?: string readonly defaultApiKey?: string readonly onProgress?: (event: OrchestratorEvent) => void diff --git a/tests/gemini-adapter.test.ts b/tests/gemini-adapter.test.ts new file mode 100644 index 0000000..7402bba --- /dev/null +++ b/tests/gemini-adapter.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +// --------------------------------------------------------------------------- +// Mock GoogleGenAI constructor (must be hoisted for Vitest) +// --------------------------------------------------------------------------- +const GoogleGenAIMock = vi.hoisted(() => vi.fn()) + +vi.mock('@google/genai', () => ({ + GoogleGenAI: GoogleGenAIMock, + FunctionCallingConfigMode: { AUTO: 'AUTO' }, +})) + +import { GeminiAdapter } from '../src/llm/gemini.js' +import { createAdapter } from '../src/llm/adapter.js' + +// --------------------------------------------------------------------------- +// GeminiAdapter tests +// --------------------------------------------------------------------------- + +describe('GeminiAdapter', () => { + beforeEach(() => { + GoogleGenAIMock.mockClear() + }) + + it('has name "gemini"', () => { + const adapter = new GeminiAdapter() + expect(adapter.name).toBe('gemini') + }) + + it('uses GEMINI_API_KEY by default', () => { + const originalGemini = process.env['GEMINI_API_KEY'] + const originalGoogle = process.env['GOOGLE_API_KEY'] + process.env['GEMINI_API_KEY'] = 'gemini-env-key' + delete process.env['GOOGLE_API_KEY'] + + try { + new GeminiAdapter() + expect(GoogleGenAIMock).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: 'gemini-env-key', + }), + ) + } finally { + if (originalGemini === undefined) { + delete process.env['GEMINI_API_KEY'] + } else { + process.env['GEMINI_API_KEY'] = originalGemini + } + if (originalGoogle === undefined) { + delete process.env['GOOGLE_API_KEY'] + } else { + process.env['GOOGLE_API_KEY'] = originalGoogle + } + } + }) + + it('falls back to GOOGLE_API_KEY when GEMINI_API_KEY is unset', () => { + const originalGemini = process.env['GEMINI_API_KEY'] + const originalGoogle = process.env['GOOGLE_API_KEY'] + delete process.env['GEMINI_API_KEY'] + process.env['GOOGLE_API_KEY'] = 'google-env-key' + + try { + new GeminiAdapter() + expect(GoogleGenAIMock).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: 'google-env-key', + }), + ) + } finally { + if (originalGemini === undefined) { + delete process.env['GEMINI_API_KEY'] + } else { + process.env['GEMINI_API_KEY'] = originalGemini + } + if (originalGoogle === undefined) { + delete process.env['GOOGLE_API_KEY'] + } else { + process.env['GOOGLE_API_KEY'] = originalGoogle + } + } + }) + + it('allows overriding apiKey explicitly', () => { + new GeminiAdapter('explicit-key') + expect(GoogleGenAIMock).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: 'explicit-key', + }), + ) + }) + + it('createAdapter("gemini") returns GeminiAdapter instance', async () => { + const adapter = await createAdapter('gemini') + expect(adapter).toBeInstanceOf(GeminiAdapter) + }) +}) diff --git a/tests/openai-fallback.test.ts b/tests/openai-fallback.test.ts new file mode 100644 index 0000000..6200146 --- /dev/null +++ b/tests/openai-fallback.test.ts @@ -0,0 +1,159 @@ +import { describe, it, expect } from 'vitest' +import { fromOpenAICompletion } from '../src/llm/openai-common.js' +import type { ChatCompletion } from 'openai/resources/chat/completions/index.js' + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function makeCompletion(overrides: { + content?: string | null + tool_calls?: ChatCompletion.Choice['message']['tool_calls'] + finish_reason?: string +}): ChatCompletion { + return { + id: 'chatcmpl-test', + object: 'chat.completion', + created: Date.now(), + model: 'test-model', + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: overrides.content ?? null, + tool_calls: overrides.tool_calls, + refusal: null, + }, + finish_reason: (overrides.finish_reason ?? 'stop') as 'stop' | 'tool_calls', + logprobs: null, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 20, + total_tokens: 30, + }, + } +} + +const TOOL_NAMES = ['bash', 'file_read', 'file_write'] + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('fromOpenAICompletion fallback extraction', () => { + it('returns normal tool_calls when present (no fallback)', () => { + const completion = makeCompletion({ + content: 'Let me run a command.', + tool_calls: [ + { + id: 'call_123', + type: 'function', + function: { + name: 'bash', + arguments: '{"command": "ls"}', + }, + }, + ], + finish_reason: 'tool_calls', + }) + + const response = fromOpenAICompletion(completion, TOOL_NAMES) + const toolBlocks = response.content.filter(b => b.type === 'tool_use') + expect(toolBlocks).toHaveLength(1) + expect(toolBlocks[0]!.type === 'tool_use' && toolBlocks[0]!.name).toBe('bash') + expect(toolBlocks[0]!.type === 'tool_use' && toolBlocks[0]!.id).toBe('call_123') + expect(response.stop_reason).toBe('tool_use') + }) + + it('extracts tool calls from text when tool_calls is absent', () => { + const completion = makeCompletion({ + content: 'I will run this:\n{"name": "bash", "arguments": {"command": "pwd"}}', + finish_reason: 'stop', + }) + + const response = fromOpenAICompletion(completion, TOOL_NAMES) + const toolBlocks = response.content.filter(b => b.type === 'tool_use') + expect(toolBlocks).toHaveLength(1) + expect(toolBlocks[0]!.type === 'tool_use' && toolBlocks[0]!.name).toBe('bash') + expect(toolBlocks[0]!.type === 'tool_use' && toolBlocks[0]!.input).toEqual({ command: 'pwd' }) + // stop_reason should be corrected to tool_use + expect(response.stop_reason).toBe('tool_use') + }) + + it('does not fallback when knownToolNames is not provided', () => { + const completion = makeCompletion({ + content: '{"name": "bash", "arguments": {"command": "ls"}}', + finish_reason: 'stop', + }) + + const response = fromOpenAICompletion(completion) + const toolBlocks = response.content.filter(b => b.type === 'tool_use') + expect(toolBlocks).toHaveLength(0) + expect(response.stop_reason).toBe('end_turn') + }) + + it('does not fallback when knownToolNames is empty', () => { + const completion = makeCompletion({ + content: '{"name": "bash", "arguments": {"command": "ls"}}', + finish_reason: 'stop', + }) + + const response = fromOpenAICompletion(completion, []) + const toolBlocks = response.content.filter(b => b.type === 'tool_use') + expect(toolBlocks).toHaveLength(0) + expect(response.stop_reason).toBe('end_turn') + }) + + it('returns plain text when no tool calls found in text', () => { + const completion = makeCompletion({ + content: 'Hello! How can I help you today?', + finish_reason: 'stop', + }) + + const response = fromOpenAICompletion(completion, TOOL_NAMES) + const toolBlocks = response.content.filter(b => b.type === 'tool_use') + expect(toolBlocks).toHaveLength(0) + expect(response.stop_reason).toBe('end_turn') + }) + + it('preserves text block alongside extracted tool blocks', () => { + const completion = makeCompletion({ + content: 'Let me check:\n{"name": "file_read", "arguments": {"path": "/tmp/x"}}', + finish_reason: 'stop', + }) + + const response = fromOpenAICompletion(completion, TOOL_NAMES) + const textBlocks = response.content.filter(b => b.type === 'text') + const toolBlocks = response.content.filter(b => b.type === 'tool_use') + expect(textBlocks).toHaveLength(1) + expect(toolBlocks).toHaveLength(1) + }) + + it('does not double-extract when native tool_calls already present', () => { + // Text also contains a tool call JSON, but native tool_calls is populated. + // The fallback should NOT run. + const completion = makeCompletion({ + content: '{"name": "file_read", "arguments": {"path": "/tmp/y"}}', + tool_calls: [ + { + id: 'call_native', + type: 'function', + function: { + name: 'bash', + arguments: '{"command": "ls"}', + }, + }, + ], + finish_reason: 'tool_calls', + }) + + const response = fromOpenAICompletion(completion, TOOL_NAMES) + const toolBlocks = response.content.filter(b => b.type === 'tool_use') + // Should only have the native one, not the text-extracted one + expect(toolBlocks).toHaveLength(1) + expect(toolBlocks[0]!.type === 'tool_use' && toolBlocks[0]!.id).toBe('call_native') + }) +}) diff --git a/tests/text-tool-extractor.test.ts b/tests/text-tool-extractor.test.ts new file mode 100644 index 0000000..dba185e --- /dev/null +++ b/tests/text-tool-extractor.test.ts @@ -0,0 +1,170 @@ +import { describe, it, expect } from 'vitest' +import { extractToolCallsFromText } from '../src/tool/text-tool-extractor.js' + +const TOOLS = ['bash', 'file_read', 'file_write'] + +describe('extractToolCallsFromText', () => { + // ------------------------------------------------------------------------- + // No tool calls + // ------------------------------------------------------------------------- + + it('returns empty array for empty text', () => { + expect(extractToolCallsFromText('', TOOLS)).toEqual([]) + }) + + it('returns empty array for plain text with no JSON', () => { + expect(extractToolCallsFromText('Hello, I am a helpful assistant.', TOOLS)).toEqual([]) + }) + + it('returns empty array for JSON that does not match any known tool', () => { + const text = '{"name": "unknown_tool", "arguments": {"x": 1}}' + expect(extractToolCallsFromText(text, TOOLS)).toEqual([]) + }) + + // ------------------------------------------------------------------------- + // Bare JSON + // ------------------------------------------------------------------------- + + it('extracts a bare JSON tool call with "arguments"', () => { + const text = 'I will run this command:\n{"name": "bash", "arguments": {"command": "ls -la"}}' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(1) + expect(result[0]!.type).toBe('tool_use') + expect(result[0]!.name).toBe('bash') + expect(result[0]!.input).toEqual({ command: 'ls -la' }) + expect(result[0]!.id).toMatch(/^extracted_call_/) + }) + + it('extracts a bare JSON tool call with "parameters"', () => { + const text = '{"name": "file_read", "parameters": {"path": "/tmp/test.txt"}}' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(1) + expect(result[0]!.name).toBe('file_read') + expect(result[0]!.input).toEqual({ path: '/tmp/test.txt' }) + }) + + it('extracts a bare JSON tool call with "input"', () => { + const text = '{"name": "bash", "input": {"command": "pwd"}}' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(1) + expect(result[0]!.name).toBe('bash') + expect(result[0]!.input).toEqual({ command: 'pwd' }) + }) + + it('extracts { function: { name, arguments } } shape', () => { + const text = '{"function": {"name": "bash", "arguments": {"command": "echo hi"}}}' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(1) + expect(result[0]!.name).toBe('bash') + expect(result[0]!.input).toEqual({ command: 'echo hi' }) + }) + + it('handles string-encoded arguments', () => { + const text = '{"name": "bash", "arguments": "{\\"command\\": \\"ls\\"}"}' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(1) + expect(result[0]!.input).toEqual({ command: 'ls' }) + }) + + // ------------------------------------------------------------------------- + // Multiple tool calls + // ------------------------------------------------------------------------- + + it('extracts multiple tool calls from text', () => { + const text = `Let me do two things: +{"name": "bash", "arguments": {"command": "ls"}} +And then: +{"name": "file_read", "arguments": {"path": "/tmp/x"}}` + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(2) + expect(result[0]!.name).toBe('bash') + expect(result[1]!.name).toBe('file_read') + }) + + // ------------------------------------------------------------------------- + // Code fence wrapped + // ------------------------------------------------------------------------- + + it('extracts tool call from markdown code fence', () => { + const text = 'Here is the tool call:\n```json\n{"name": "bash", "arguments": {"command": "whoami"}}\n```' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(1) + expect(result[0]!.name).toBe('bash') + expect(result[0]!.input).toEqual({ command: 'whoami' }) + }) + + it('extracts tool call from code fence without language tag', () => { + const text = '```\n{"name": "file_write", "arguments": {"path": "/tmp/a.txt", "content": "hi"}}\n```' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(1) + expect(result[0]!.name).toBe('file_write') + }) + + // ------------------------------------------------------------------------- + // Hermes format + // ------------------------------------------------------------------------- + + it('extracts tool call from tags', () => { + const text = '\n{"name": "bash", "arguments": {"command": "date"}}\n' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(1) + expect(result[0]!.name).toBe('bash') + expect(result[0]!.input).toEqual({ command: 'date' }) + }) + + it('extracts multiple hermes tool calls', () => { + const text = `{"name": "bash", "arguments": {"command": "ls"}} +Some text in between +{"name": "file_read", "arguments": {"path": "/tmp/x"}}` + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(2) + expect(result[0]!.name).toBe('bash') + expect(result[1]!.name).toBe('file_read') + }) + + // ------------------------------------------------------------------------- + // Edge cases + // ------------------------------------------------------------------------- + + it('skips malformed JSON gracefully', () => { + const text = '{"name": "bash", "arguments": {invalid json}}' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toEqual([]) + }) + + it('skips JSON objects without a name field', () => { + const text = '{"command": "ls", "arguments": {"x": 1}}' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toEqual([]) + }) + + it('works with empty knownToolNames (no whitelist filtering)', () => { + const text = '{"name": "anything", "arguments": {"x": 1}}' + const result = extractToolCallsFromText(text, []) + expect(result).toHaveLength(1) + expect(result[0]!.name).toBe('anything') + }) + + it('generates unique IDs for each extracted call', () => { + const text = `{"name": "bash", "arguments": {"command": "a"}} +{"name": "bash", "arguments": {"command": "b"}}` + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(2) + expect(result[0]!.id).not.toBe(result[1]!.id) + }) + + it('handles tool call with no arguments', () => { + const text = '{"name": "bash"}' + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(1) + expect(result[0]!.input).toEqual({}) + }) + + it('handles text with nested JSON objects that are not tool calls', () => { + const text = `Here is some config: {"port": 3000, "host": "localhost"} +And a tool call: {"name": "bash", "arguments": {"command": "ls"}}` + const result = extractToolCallsFromText(text, TOOLS) + expect(result).toHaveLength(1) + expect(result[0]!.name).toBe('bash') + }) +})