mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2026-01-30 06:12:05 +00:00
feat(gemini-cli): upgrade to native structured output support (#1437)
This commit is contained in:
9
.changeset/gemini-cli-native-structured-output.md
Normal file
9
.changeset/gemini-cli-native-structured-output.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Upgrade gemini-cli provider to native structured output support
|
||||||
|
|
||||||
|
- Upgrade `ai-sdk-provider-gemini-cli` from v1.1.1 to v1.4.0 with native `responseJsonSchema` support
|
||||||
|
- Simplify provider implementation by removing JSON extraction workarounds (652 lines → 95 lines)
|
||||||
|
- Enable native structured output via Gemini API's schema enforcement
|
||||||
@@ -170,7 +170,7 @@ Example:
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Node.js 18+
|
- Node.js 20+
|
||||||
- npm or yarn
|
- npm or yarn
|
||||||
|
|
||||||
### Environment Setup
|
### Environment Setup
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"vitest": "^4.0.10"
|
"vitest": "^4.0.10"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"keywords": ["task-master", "cli", "task-management", "productivity"],
|
"keywords": ["task-master", "cli", "task-management", "productivity"],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ Example:
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Node.js 18+
|
- Node.js 20+
|
||||||
- npm or yarn
|
- npm or yarn
|
||||||
|
|
||||||
### Environment Setup
|
### Environment Setup
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Taskmaster AI is an intelligent task management system designed for AI-assisted
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
1. **VS Code** 1.90.0 or higher
|
1. **VS Code** 1.90.0 or higher
|
||||||
2. **Node.js** 18.0 or higher (for Taskmaster MCP server)
|
2. **Node.js** 20.0 or higher (for Taskmaster MCP server)
|
||||||
|
|
||||||
### Install the Extension
|
### Install the Extension
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ Access settings via **File → Preferences → Settings** and search for "Taskma
|
|||||||
## 🔧 Troubleshooting
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
### **Extension Not Loading**
|
### **Extension Not Loading**
|
||||||
1. Ensure Node.js 18+ is installed
|
1. Ensure Node.js 20+ is installed
|
||||||
2. Check workspace contains `.taskmaster` folder
|
2. Check workspace contains `.taskmaster` folder
|
||||||
3. Restart VS Code
|
3. Restart VS Code
|
||||||
4. Check Output panel (View → Output → Taskmaster Kanban)
|
4. Check Output panel (View → Output → Taskmaster Kanban)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"vitest": "^4.0.10"
|
"vitest": "^4.0.10"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"task-master",
|
"task-master",
|
||||||
|
|||||||
@@ -505,7 +505,7 @@ Azure OpenAI provides enterprise-grade OpenAI models through Microsoft's Azure c
|
|||||||
The Codex CLI provider integrates Task Master with OpenAI's Codex CLI, allowing you to use ChatGPT subscription models via OAuth authentication.
|
The Codex CLI provider integrates Task Master with OpenAI's Codex CLI, allowing you to use ChatGPT subscription models via OAuth authentication.
|
||||||
|
|
||||||
1. **Prerequisites**:
|
1. **Prerequisites**:
|
||||||
- Node.js >= 18
|
- Node.js >= 20
|
||||||
- Codex CLI >= 0.42.0 (>= 0.44.0 recommended)
|
- Codex CLI >= 0.42.0 (>= 0.44.0 recommended)
|
||||||
- ChatGPT subscription: Plus, Pro, Business, Edu, or Enterprise (for OAuth access to GPT-5 models)
|
- ChatGPT subscription: Plus, Pro, Business, Edu, or Enterprise (for OAuth access to GPT-5 models)
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ task-master models --set-main gpt-5-codex --codex-cli
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- **Node.js**: >= 18.0.0
|
- **Node.js**: >= 20.0.0
|
||||||
- **Codex CLI**: >= 0.42.0 (>= 0.44.0 recommended)
|
- **Codex CLI**: >= 0.42.0 (>= 0.44.0 recommended)
|
||||||
- **ChatGPT Subscription**: Required for OAuth access (Plus, Pro, Business, Edu, or Enterprise)
|
- **ChatGPT Subscription**: Required for OAuth access (Plus, Pro, Business, Edu, or Enterprise)
|
||||||
- **Task Master**: >= 0.27.3 (version with Codex CLI support)
|
- **Task Master**: >= 0.27.3 (version with Codex CLI support)
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ You can also manually edit your `.taskmaster/config.json`:
|
|||||||
|
|
||||||
### Available Models
|
### Available Models
|
||||||
|
|
||||||
The gemini-cli provider supports only two models:
|
The gemini-cli provider supports the following models:
|
||||||
|
- `gemini-3-pro-preview` - Latest preview model with best performance
|
||||||
- `gemini-2.5-pro` - High performance model (1M token context window, 65,536 max output tokens)
|
- `gemini-2.5-pro` - High performance model (1M token context window, 65,536 max output tokens)
|
||||||
- `gemini-2.5-flash` - Fast, efficient model (1M token context window, 65,536 max output tokens)
|
- `gemini-2.5-flash` - Fast, efficient model (1M token context window, 65,536 max output tokens)
|
||||||
|
|
||||||
@@ -134,7 +135,8 @@ If you get an authentication error:
|
|||||||
|
|
||||||
### "Model not found" Error
|
### "Model not found" Error
|
||||||
|
|
||||||
The gemini-cli provider only supports two models:
|
The gemini-cli provider supports the following models:
|
||||||
|
- `gemini-3-pro-preview`
|
||||||
- `gemini-2.5-pro`
|
- `gemini-2.5-pro`
|
||||||
- `gemini-2.5-flash`
|
- `gemini-2.5-flash`
|
||||||
|
|
||||||
@@ -152,10 +154,45 @@ npm install -g @google/gemini-cli
|
|||||||
gemini --version
|
gemini --version
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Native Structured Outputs (v1.4.0+)
|
||||||
|
|
||||||
|
As of `ai-sdk-provider-gemini-cli` v1.4.0, the Gemini CLI provider now supports **native structured output** via Gemini's `responseJsonSchema` parameter. This provides several benefits:
|
||||||
|
|
||||||
|
### Key Benefits
|
||||||
|
|
||||||
|
- **Guaranteed Schema Compliance**: JSON output is constrained at the API level to match your schema
|
||||||
|
- **No JSON Parsing Errors**: Eliminates issues with malformed JSON or conversational preamble
|
||||||
|
- **Improved Reliability**: Native schema enforcement means consistent, predictable output
|
||||||
|
- **Better Performance**: No need for post-processing or JSON extraction from text
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
When you use Task Master commands that require structured output (like `parse-prd`, `expand`, `add-task`, `update-task`, or `analyze-complexity`), the provider:
|
||||||
|
|
||||||
|
1. Passes the Zod schema directly to Gemini's API via `responseJsonSchema`
|
||||||
|
2. Sets `responseMimeType: 'application/json'` for clean JSON output
|
||||||
|
3. Returns validated, schema-compliant JSON without any text extraction needed
|
||||||
|
|
||||||
|
### Supported Commands
|
||||||
|
|
||||||
|
All commands that use structured output benefit from native schema enforcement:
|
||||||
|
|
||||||
|
- `task-master parse-prd` - Parse PRD and generate tasks
|
||||||
|
- `task-master expand` - Expand tasks into subtasks
|
||||||
|
- `task-master add-task` - Add new tasks with AI assistance
|
||||||
|
- `task-master update-task` - Update existing tasks
|
||||||
|
- `task-master analyze-complexity` - Analyze task complexity
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- **Node.js 20+**: The v1.4.0 SDK requires Node.js 20 or later
|
||||||
|
- **ai-sdk-provider-gemini-cli >= 1.4.0**: Included with Task Master automatically
|
||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
||||||
- **OAuth vs API Key**: This provider is specifically designed for users who want to use OAuth authentication via gemini-cli. If you prefer using API keys, consider using the standard `google` provider instead.
|
- **OAuth vs API Key**: This provider is specifically designed for users who want to use OAuth authentication via gemini-cli. If you prefer using API keys, consider using the standard `google` provider instead.
|
||||||
- **Limited Model Support**: Only `gemini-2.5-pro` and `gemini-2.5-flash` are available through gemini-cli.
|
- **Limited Model Support**: Only `gemini-3-pro-preview`, `gemini-2.5-pro`, and `gemini-2.5-flash` are available through gemini-cli.
|
||||||
- **Subscription Benefits**: Using OAuth authentication allows you to leverage any subscription benefits associated with your Google account.
|
- **Subscription Benefits**: Using OAuth authentication allows you to leverage any subscription benefits associated with your Google account.
|
||||||
|
- **Node.js Requirement**: Requires Node.js 20+ due to native structured output support.
|
||||||
- The provider uses the `ai-sdk-provider-gemini-cli` npm package internally.
|
- The provider uses the `ai-sdk-provider-gemini-cli` npm package internally.
|
||||||
- Supports all standard Task Master features: text generation, streaming, and structured object generation.
|
- Supports all standard Task Master features: text generation, streaming, and structured object generation with native schema enforcement.
|
||||||
413
package-lock.json
generated
413
package-lock.json
generated
@@ -35,7 +35,7 @@
|
|||||||
"ai": "^5.0.51",
|
"ai": "^5.0.51",
|
||||||
"ai-sdk-provider-claude-code": "^2.2.0",
|
"ai-sdk-provider-claude-code": "^2.2.0",
|
||||||
"ai-sdk-provider-codex-cli": "^0.3.0",
|
"ai-sdk-provider-codex-cli": "^0.3.0",
|
||||||
"ai-sdk-provider-gemini-cli": "^1.1.1",
|
"ai-sdk-provider-gemini-cli": "^1.4.0",
|
||||||
"ajv": "^8.17.1",
|
"ajv": "^8.17.1",
|
||||||
"ajv-formats": "^3.0.1",
|
"ajv-formats": "^3.0.1",
|
||||||
"boxen": "^8.0.1",
|
"boxen": "^8.0.1",
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@anthropic-ai/claude-code": "^1.0.88",
|
"@anthropic-ai/claude-code": "^1.0.88",
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
"vitest": "^4.0.10"
|
"vitest": "^4.0.10"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"apps/cli/node_modules/@types/node": {
|
"apps/cli/node_modules/@types/node": {
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
"vitest": "^4.0.10"
|
"vitest": "^4.0.10"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"apps/mcp/node_modules/@types/node": {
|
"apps/mcp/node_modules/@types/node": {
|
||||||
@@ -1563,7 +1563,6 @@
|
|||||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
@@ -2798,7 +2797,6 @@
|
|||||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/accessibility": "^3.1.1",
|
"@dnd-kit/accessibility": "^3.1.1",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
@@ -3496,9 +3494,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@google/gemini-cli-core": {
|
"node_modules/@google/gemini-cli-core": {
|
||||||
"version": "0.16.0",
|
"version": "0.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/@google/gemini-cli-core/-/gemini-cli-core-0.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@google/gemini-cli-core/-/gemini-cli-core-0.17.1.tgz",
|
||||||
"integrity": "sha512-EYzcAUcIcfkLJQGHabS96Y47A9ofEapzgJwLtbzpUwYFBuAegQcnl3xhbdxfj6kCygVHq2rPoa/udEVfqryOjQ==",
|
"integrity": "sha512-XL6cw7iksZGG36Na2dwI7rRtdpI+cHRbr2I3mcel5ApXkeIhcSzH6ojbk69bK/f2DUSB/OrJJDlonh5+sPTzOg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google-cloud/logging": "^11.2.1",
|
"@google-cloud/logging": "^11.2.1",
|
||||||
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
|
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
|
||||||
@@ -3529,7 +3527,7 @@
|
|||||||
"fast-uri": "^3.0.6",
|
"fast-uri": "^3.0.6",
|
||||||
"fdir": "^6.4.6",
|
"fdir": "^6.4.6",
|
||||||
"fzf": "^0.5.2",
|
"fzf": "^0.5.2",
|
||||||
"glob": "^10.4.5",
|
"glob": "^12.0.0",
|
||||||
"google-auth-library": "^9.11.0",
|
"google-auth-library": "^9.11.0",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
@@ -3562,27 +3560,6 @@
|
|||||||
"node-pty": "^1.0.0"
|
"node-pty": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@google/gemini-cli-core/node_modules/@google/genai": {
|
|
||||||
"version": "1.16.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.16.0.tgz",
|
|
||||||
"integrity": "sha512-hdTYu39QgDFxv+FB6BK2zi4UIJGWhx2iPc0pHQ0C5Q/RCi+m+4gsryIzTGO+riqWcUA8/WGYp6hpqckdOBNysw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"google-auth-library": "^9.14.2",
|
|
||||||
"ws": "^8.18.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@modelcontextprotocol/sdk": "^1.11.4"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@modelcontextprotocol/sdk": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@google/gemini-cli-core/node_modules/dotenv": {
|
"node_modules/@google/gemini-cli-core/node_modules/dotenv": {
|
||||||
"version": "17.2.3",
|
"version": "17.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||||
@@ -3595,6 +3572,53 @@
|
|||||||
"url": "https://dotenvx.com"
|
"url": "https://dotenvx.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@google/gemini-cli-core/node_modules/glob": {
|
||||||
|
"version": "12.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-12.0.0.tgz",
|
||||||
|
"integrity": "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"foreground-child": "^3.3.1",
|
||||||
|
"jackspeak": "^4.1.1",
|
||||||
|
"minimatch": "^10.1.1",
|
||||||
|
"minipass": "^7.1.2",
|
||||||
|
"package-json-from-dist": "^1.0.0",
|
||||||
|
"path-scurry": "^2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"glob": "dist/esm/bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@google/gemini-cli-core/node_modules/jackspeak": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@isaacs/cliui": "^8.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@google/gemini-cli-core/node_modules/lru-cache": {
|
||||||
|
"version": "11.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
|
||||||
|
"integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@google/gemini-cli-core/node_modules/mime": {
|
"node_modules/@google/gemini-cli-core/node_modules/mime": {
|
||||||
"version": "4.0.7",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz",
|
||||||
@@ -3610,6 +3634,37 @@
|
|||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@google/gemini-cli-core/node_modules/minimatch": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@isaacs/brace-expansion": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@google/gemini-cli-core/node_modules/path-scurry": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^11.0.0",
|
||||||
|
"minipass": "^7.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@google/gemini-cli-core/node_modules/zod": {
|
"node_modules/@google/gemini-cli-core/node_modules/zod": {
|
||||||
"version": "3.25.76",
|
"version": "3.25.76",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
@@ -3620,9 +3675,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@google/genai": {
|
"node_modules/@google/genai": {
|
||||||
"version": "1.14.0",
|
"version": "1.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.16.0.tgz",
|
||||||
"integrity": "sha512-jirYprAAJU1svjwSDVCzyVq+FrJpJd5CSxR/g2Ga/gZ0ZYZpcWjMS75KJl9y71K1mDN+tcx6s21CzCbB2R840g==",
|
"integrity": "sha512-hdTYu39QgDFxv+FB6BK2zi4UIJGWhx2iPc0pHQ0C5Q/RCi+m+4gsryIzTGO+riqWcUA8/WGYp6hpqckdOBNysw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"google-auth-library": "^9.14.2",
|
"google-auth-library": "^9.14.2",
|
||||||
@@ -3632,7 +3687,7 @@
|
|||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.11.0"
|
"@modelcontextprotocol/sdk": "^1.11.4"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@modelcontextprotocol/sdk": {
|
"@modelcontextprotocol/sdk": {
|
||||||
@@ -4380,6 +4435,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@isaacs/balanced-match": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@isaacs/brace-expansion": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@isaacs/balanced-match": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@@ -6033,7 +6109,6 @@
|
|||||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
@@ -6165,6 +6240,7 @@
|
|||||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
@@ -6870,6 +6946,7 @@
|
|||||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
@@ -7327,7 +7404,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
@@ -7361,7 +7437,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz",
|
||||||
"integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==",
|
"integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||||
},
|
},
|
||||||
@@ -8326,7 +8401,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz",
|
||||||
"integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==",
|
"integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.2.0",
|
"@opentelemetry/core": "2.2.0",
|
||||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||||
@@ -8619,6 +8693,7 @@
|
|||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -8670,14 +8745,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@polka/url": {
|
|
||||||
"version": "1.0.0-next.29",
|
|
||||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
|
|
||||||
"integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/@protobufjs/aspromise": {
|
"node_modules/@protobufjs/aspromise": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||||
@@ -9265,6 +9332,7 @@
|
|||||||
"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
|
"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-primitive": "2.1.3",
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||||
@@ -9290,6 +9358,7 @@
|
|||||||
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-compose-refs": "1.1.2"
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
},
|
},
|
||||||
@@ -12304,7 +12373,6 @@
|
|||||||
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -12315,7 +12383,6 @@
|
|||||||
"integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
|
"integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.0.0"
|
"@types/react": "^19.0.0"
|
||||||
}
|
}
|
||||||
@@ -12963,7 +13030,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -13041,7 +13107,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.97.tgz",
|
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.97.tgz",
|
||||||
"integrity": "sha512-8zBx0b/owis4eJI2tAlV8a1Rv0BANmLxontcAelkLNwEHhgfgXeKpDkhNB6OgV+BJSwboIUDkgd9312DdJnCOQ==",
|
"integrity": "sha512-8zBx0b/owis4eJI2tAlV8a1Rv0BANmLxontcAelkLNwEHhgfgXeKpDkhNB6OgV+BJSwboIUDkgd9312DdJnCOQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/gateway": "2.0.12",
|
"@ai-sdk/gateway": "2.0.12",
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "2.0.0",
|
||||||
@@ -13148,17 +13213,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ai-sdk-provider-gemini-cli": {
|
"node_modules/ai-sdk-provider-gemini-cli": {
|
||||||
"version": "1.3.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/ai-sdk-provider-gemini-cli/-/ai-sdk-provider-gemini-cli-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ai-sdk-provider-gemini-cli/-/ai-sdk-provider-gemini-cli-1.4.0.tgz",
|
||||||
"integrity": "sha512-I9QNtTiQxpiF9QnSStKaKLDj1z9Ei3vDNKX9huiCJUwAJXl0kinH/MZLxU/CaUFhADlWq1qFmff85jqwXamqCw==",
|
"integrity": "sha512-k5nWGbqUO28Bi8yQErBP1m5MQZQL1oCVFpCytSepYMAwfNP01QPcYyYR2bt8HrGGlEMS7Bl1CbuRjPFcJdccMg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "2.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.3",
|
"@ai-sdk/provider-utils": "3.0.17",
|
||||||
"@google/gemini-cli-core": "0.16.0",
|
"@google/gemini-cli-core": "0.17.1",
|
||||||
"@google/genai": "1.14.0",
|
"@google/genai": "1.16.0",
|
||||||
"google-auth-library": "10.2.1",
|
"google-auth-library": "^9.11.0",
|
||||||
"zod-to-json-schema": "3.24.6"
|
"zod-to-json-schema": "3.25.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
@@ -13167,108 +13232,11 @@
|
|||||||
"zod": "^3.0.0 || ^4.0.0"
|
"zod": "^3.0.0 || ^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ai-sdk-provider-gemini-cli/node_modules/@ai-sdk/provider-utils": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-kAxIw1nYmFW1g5TvE54ZB3eNtgZna0RnLjPUp1ltz1+t9xkXJIuDT4atrwfau9IbS0BOef38wqrI8CjFfQrxhw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@ai-sdk/provider": "2.0.0",
|
|
||||||
"@standard-schema/spec": "^1.0.0",
|
|
||||||
"eventsource-parser": "^3.0.3",
|
|
||||||
"zod-to-json-schema": "^3.24.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.25.76 || ^4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ai-sdk-provider-gemini-cli/node_modules/gaxios": {
|
|
||||||
"version": "7.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz",
|
|
||||||
"integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"extend": "^3.0.2",
|
|
||||||
"https-proxy-agent": "^7.0.1",
|
|
||||||
"node-fetch": "^3.3.2",
|
|
||||||
"rimraf": "^5.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ai-sdk-provider-gemini-cli/node_modules/gcp-metadata": {
|
|
||||||
"version": "7.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz",
|
|
||||||
"integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==",
|
|
||||||
"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/ai-sdk-provider-gemini-cli/node_modules/google-auth-library": {
|
|
||||||
"version": "10.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.2.1.tgz",
|
|
||||||
"integrity": "sha512-HMxFl2NfeHYnaL1HoRIN1XgorKS+6CDaM+z9LSSN+i/nKDDL4KFFEWogMXu7jV4HZQy2MsxpY+wA5XIf3w410A==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"base64-js": "^1.3.0",
|
|
||||||
"ecdsa-sig-formatter": "^1.0.11",
|
|
||||||
"gaxios": "^7.0.0",
|
|
||||||
"gcp-metadata": "^7.0.0",
|
|
||||||
"google-logging-utils": "^1.0.0",
|
|
||||||
"gtoken": "^8.0.0",
|
|
||||||
"jws": "^4.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ai-sdk-provider-gemini-cli/node_modules/google-logging-utils": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
|
|
||||||
"integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ai-sdk-provider-gemini-cli/node_modules/gtoken": {
|
|
||||||
"version": "8.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz",
|
|
||||||
"integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"gaxios": "^7.0.0",
|
|
||||||
"jws": "^4.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ai-sdk-provider-gemini-cli/node_modules/zod-to-json-schema": {
|
|
||||||
"version": "3.24.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
|
|
||||||
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.24.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "8.17.1",
|
"version": "8.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fast-uri": "^3.0.1",
|
"fast-uri": "^3.0.1",
|
||||||
@@ -14019,6 +13987,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/bare-events": {
|
"node_modules/bare-events": {
|
||||||
@@ -14430,6 +14399,7 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
@@ -14475,7 +14445,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.8.25",
|
"baseline-browser-mapping": "^2.8.25",
|
||||||
"caniuse-lite": "^1.0.30001754",
|
"caniuse-lite": "^1.0.30001754",
|
||||||
@@ -16702,8 +16671,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz",
|
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz",
|
||||||
"integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==",
|
"integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/dezalgo": {
|
"node_modules/dezalgo": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
@@ -17418,7 +17386,6 @@
|
|||||||
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
@@ -18626,14 +18593,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fflate": {
|
|
||||||
"version": "0.8.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
|
||||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/figlet": {
|
"node_modules/figlet": {
|
||||||
"version": "1.9.4",
|
"version": "1.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.9.4.tgz",
|
||||||
@@ -18806,14 +18765,6 @@
|
|||||||
"flat": "cli.js"
|
"flat": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/flatted": {
|
|
||||||
"version": "3.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
|
|
||||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.15.11",
|
"version": "1.15.11",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
@@ -19320,6 +19271,7 @@
|
|||||||
"version": "10.5.0",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"foreground-child": "^3.1.0",
|
"foreground-child": "^3.1.0",
|
||||||
@@ -19533,9 +19485,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/got": {
|
"node_modules/got": {
|
||||||
"version": "14.6.4",
|
"version": "14.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/got/-/got-14.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/got/-/got-14.6.5.tgz",
|
||||||
"integrity": "sha512-DjsLab39NUMf5iYlK9asVCkHMhaA2hEhrlmf+qXRhjEivuuBHWYbjmty9DA3OORUwZgENTB+6vSmY2ZW8gFHVw==",
|
"integrity": "sha512-Su87c0NNeg97de1sO02gy9I8EmE7DCJ1gzcFLcgGpYeq2PnLg4xz73MWrp6HjqbSsjb6Glf4UBDW6JNyZA6uSg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sindresorhus/is": "^7.0.1",
|
"@sindresorhus/is": "^7.0.1",
|
||||||
@@ -20540,7 +20492,6 @@
|
|||||||
"integrity": "sha512-wF3j/DmkM8q5E+OtfdQhCRw8/0ahkc8CUTgEddxZzpEWPslu7YPL3t64MWRoI9m6upVGpfAg4ms2BBvxCdKRLQ==",
|
"integrity": "sha512-wF3j/DmkM8q5E+OtfdQhCRw8/0ahkc8CUTgEddxZzpEWPslu7YPL3t64MWRoI9m6upVGpfAg4ms2BBvxCdKRLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alcalzone/ansi-tokenize": "^0.2.1",
|
"@alcalzone/ansi-tokenize": "^0.2.1",
|
||||||
"ansi-escapes": "^7.2.0",
|
"ansi-escapes": "^7.2.0",
|
||||||
@@ -21752,6 +21703,7 @@
|
|||||||
"version": "3.4.3",
|
"version": "3.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||||
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/cliui": "^8.0.2"
|
"@isaacs/cliui": "^8.0.2"
|
||||||
@@ -21769,7 +21721,6 @@
|
|||||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/core": "^29.7.0",
|
"@jest/core": "^29.7.0",
|
||||||
"@jest/types": "^29.6.3",
|
"@jest/types": "^29.6.3",
|
||||||
@@ -23641,7 +23592,6 @@
|
|||||||
"integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==",
|
"integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.16.0"
|
"node": ">= 10.16.0"
|
||||||
}
|
}
|
||||||
@@ -24051,6 +24001,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24072,6 +24023,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24093,6 +24045,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24114,6 +24067,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24135,6 +24089,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24156,6 +24111,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24177,6 +24133,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24198,6 +24155,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24219,6 +24177,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24240,6 +24199,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24261,6 +24221,7 @@
|
|||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
},
|
},
|
||||||
@@ -24539,6 +24500,7 @@
|
|||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
},
|
},
|
||||||
@@ -24551,7 +24513,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/lowercase-keys": {
|
"node_modules/lowercase-keys": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@@ -24698,7 +24661,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
|
||||||
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
|
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"marked": "bin/marked.js"
|
"marked": "bin/marked.js"
|
||||||
},
|
},
|
||||||
@@ -26078,6 +26040,7 @@
|
|||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"brace-expansion": "^2.0.1"
|
||||||
@@ -26425,17 +26388,6 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mrmime": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -27692,6 +27644,7 @@
|
|||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||||
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
@@ -27864,7 +27817,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -28597,7 +28549,6 @@
|
|||||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -28608,7 +28559,6 @@
|
|||||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -29587,28 +29537,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rimraf": {
|
|
||||||
"version": "5.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
|
||||||
"integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"glob": "^10.3.7"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"rimraf": "dist/esm/bin.mjs"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rolldown": {
|
"node_modules/rolldown": {
|
||||||
"version": "1.0.0-beta.45",
|
"version": "1.0.0-beta.45",
|
||||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.45.tgz",
|
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.45.tgz",
|
||||||
"integrity": "sha512-iMmuD72XXLf26Tqrv1cryNYLX6NNPLhZ3AmNkSf8+xda0H+yijjGJ+wVT9UdBUHOpKzq9RjKtQKRCWoEKQQBZQ==",
|
"integrity": "sha512-iMmuD72XXLf26Tqrv1cryNYLX6NNPLhZ3AmNkSf8+xda0H+yijjGJ+wVT9UdBUHOpKzq9RjKtQKRCWoEKQQBZQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oxc-project/types": "=0.95.0",
|
"@oxc-project/types": "=0.95.0",
|
||||||
"@rolldown/pluginutils": "1.0.0-beta.45"
|
"@rolldown/pluginutils": "1.0.0-beta.45"
|
||||||
@@ -30421,22 +30355,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/sirv": {
|
|
||||||
"version": "3.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
|
|
||||||
"integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@polka/url": "^1.0.0-next.24",
|
|
||||||
"mrmime": "^2.0.0",
|
|
||||||
"totalist": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/sisteransi": {
|
"node_modules/sisteransi": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||||
@@ -31778,17 +31696,6 @@
|
|||||||
"url": "https://github.com/sponsors/Borewit"
|
"url": "https://github.com/sponsors/Borewit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/totalist": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tr46": {
|
"node_modules/tr46": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
|
||||||
@@ -32940,7 +32847,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -33099,7 +33005,6 @@
|
|||||||
"integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
|
"integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/unist": "^3.0.0",
|
"@types/unist": "^3.0.0",
|
||||||
"bail": "^2.0.0",
|
"bail": "^2.0.0",
|
||||||
@@ -33614,7 +33519,6 @@
|
|||||||
"integrity": "sha512-pmW4GCKQ8t5Ko1jYjC3SqOr7TUKN7uHOHB/XGsAIb69eYu6d1ionGSsb5H9chmPf+WeXt0VE7jTXsB1IvWoNbw==",
|
"integrity": "sha512-pmW4GCKQ8t5Ko1jYjC3SqOr7TUKN7uHOHB/XGsAIb69eYu6d1ionGSsb5H9chmPf+WeXt0VE7jTXsB1IvWoNbw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "4.0.12",
|
"@vitest/expect": "4.0.12",
|
||||||
"@vitest/mocker": "4.0.12",
|
"@vitest/mocker": "4.0.12",
|
||||||
@@ -33731,7 +33635,6 @@
|
|||||||
"integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
|
"integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -33801,6 +33704,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitest/node_modules/yaml": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/walker": {
|
"node_modules/walker": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
||||||
@@ -34377,7 +34295,7 @@
|
|||||||
"version": "1.10.2",
|
"version": "1.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -34549,7 +34467,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
|
||||||
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
|
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -34587,7 +34504,7 @@
|
|||||||
"vitest": "^4.0.10"
|
"vitest": "^4.0.10"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/ai-sdk-provider-grok-cli/node_modules/@types/node": {
|
"packages/ai-sdk-provider-grok-cli/node_modules/@types/node": {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
"ai": "^5.0.51",
|
"ai": "^5.0.51",
|
||||||
"ai-sdk-provider-claude-code": "^2.2.0",
|
"ai-sdk-provider-claude-code": "^2.2.0",
|
||||||
"ai-sdk-provider-codex-cli": "^0.3.0",
|
"ai-sdk-provider-codex-cli": "^0.3.0",
|
||||||
"ai-sdk-provider-gemini-cli": "^1.1.1",
|
"ai-sdk-provider-gemini-cli": "^1.4.0",
|
||||||
"ajv": "^8.17.1",
|
"ajv": "^8.17.1",
|
||||||
"ajv-formats": "^3.0.1",
|
"ajv-formats": "^3.0.1",
|
||||||
"boxen": "^8.0.1",
|
"boxen": "^8.0.1",
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
"@biomejs/cli-linux-x64": "^1.9.4"
|
"@biomejs/cli-linux-x64": "^1.9.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"packageManager": "npm@10.9.2",
|
"packageManager": "npm@10.9.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"vitest": "^4.0.10"
|
"vitest": "^4.0.10"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"keywords": ["ai", "grok", "x.ai", "cli", "language-model", "provider"],
|
"keywords": ["ai", "grok", "x.ai", "cli", "language-model", "provider"],
|
||||||
"files": ["dist/**/*", "README.md"],
|
"files": ["dist/**/*", "README.md"],
|
||||||
|
|||||||
@@ -91,10 +91,10 @@ npm install -g task-master-ai --registry https://registry.npmjs.org/
|
|||||||
|
|
||||||
**Node Version Issues:**
|
**Node Version Issues:**
|
||||||
```bash
|
```bash
|
||||||
# Install Node 18+ via nvm
|
# Install Node 20+ via nvm
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
||||||
nvm install 18
|
nvm install 20
|
||||||
nvm use 18
|
nvm use 20
|
||||||
```
|
```
|
||||||
|
|
||||||
## Success Confirmation
|
## Success Confirmation
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ The library is organized into several key modules:
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Node.js >= 18.0.0
|
- Node.js >= 20.0.0
|
||||||
- npm or yarn
|
- npm or yarn
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|||||||
@@ -3,35 +3,38 @@
|
|||||||
*
|
*
|
||||||
* Implementation for interacting with Gemini models via Gemini CLI
|
* Implementation for interacting with Gemini models via Gemini CLI
|
||||||
* using the ai-sdk-provider-gemini-cli package.
|
* using the ai-sdk-provider-gemini-cli package.
|
||||||
|
*
|
||||||
|
* As of v1.4.0, the SDK provides native structured output support via:
|
||||||
|
* - supportsStructuredOutputs = true
|
||||||
|
* - defaultObjectGenerationMode = 'json'
|
||||||
|
* - responseJsonSchema passed directly to Gemini API
|
||||||
|
*
|
||||||
|
* This eliminates the need for JSON extraction workarounds.
|
||||||
|
* System messages are automatically handled by the SDK's mapPromptToGeminiFormat
|
||||||
|
* which extracts them to Gemini's systemInstruction field.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { generateObject, generateText, streamText } from 'ai';
|
|
||||||
import { parse } from 'jsonc-parser';
|
|
||||||
import { BaseAIProvider } from './base-provider.js';
|
|
||||||
import { log } from '../../scripts/modules/utils.js';
|
|
||||||
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';
|
import { createGeminiProvider } from 'ai-sdk-provider-gemini-cli';
|
||||||
|
import { BaseAIProvider } from './base-provider.js';
|
||||||
|
|
||||||
export class GeminiCliProvider extends BaseAIProvider {
|
export class GeminiCliProvider extends BaseAIProvider {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.name = 'Gemini CLI';
|
this.name = 'Gemini CLI';
|
||||||
// Gemini CLI requires explicit JSON schema mode
|
|
||||||
this.needsExplicitJsonSchema = true;
|
|
||||||
// Gemini CLI does not support temperature parameter
|
// Gemini CLI does not support temperature parameter
|
||||||
this.supportsTemperature = false;
|
this.supportsTemperature = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override validateAuth to handle Gemini CLI authentication options
|
* Override validateAuth to handle Gemini CLI authentication options.
|
||||||
|
* Gemini CLI is designed to use pre-configured OAuth authentication.
|
||||||
|
* Users choose gemini-cli specifically to leverage their existing
|
||||||
|
* gemini auth login credentials, not to use API keys.
|
||||||
* @param {object} params - Parameters to validate
|
* @param {object} params - Parameters to validate
|
||||||
*/
|
*/
|
||||||
validateAuth(params) {
|
validateAuth(params) {
|
||||||
// Gemini CLI is designed to use pre-configured OAuth authentication
|
|
||||||
// Users choose gemini-cli specifically to leverage their existing
|
|
||||||
// gemini auth login credentials, not to use API keys.
|
|
||||||
// We support API keys for compatibility, but the expected usage
|
|
||||||
// is through CLI authentication (no API key required).
|
|
||||||
// No validation needed - the SDK will handle auth internally
|
// No validation needed - the SDK will handle auth internally
|
||||||
|
// via OAuth (primary) or API key (optional fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +47,7 @@ export class GeminiCliProvider extends BaseAIProvider {
|
|||||||
*/
|
*/
|
||||||
async getClient(params) {
|
async getClient(params) {
|
||||||
try {
|
try {
|
||||||
// Primary use case: Use existing gemini CLI authentication
|
// Primary use case: Use existing gemini CLI authentication via OAuth
|
||||||
// Secondary use case: Direct API key (for compatibility)
|
// Secondary use case: Direct API key (for compatibility)
|
||||||
let authOptions = {};
|
let authOptions = {};
|
||||||
|
|
||||||
@@ -74,577 +77,18 @@ export class GeminiCliProvider extends BaseAIProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts system messages from the messages array and returns them separately.
|
* Returns the name of the API key environment variable.
|
||||||
* This is needed because ai-sdk-provider-gemini-cli expects system prompts as a separate parameter.
|
* @returns {string} API key environment variable name
|
||||||
* @param {Array} messages - Array of message objects
|
|
||||||
* @param {Object} options - Options for system prompt enhancement
|
|
||||||
* @param {boolean} options.enforceJsonOutput - Whether to add JSON enforcement to system prompt
|
|
||||||
* @returns {Object} - {systemPrompt: string|undefined, messages: Array}
|
|
||||||
*/
|
*/
|
||||||
_extractSystemMessage(messages, options = {}) {
|
|
||||||
if (!messages || !Array.isArray(messages)) {
|
|
||||||
return { systemPrompt: undefined, messages: messages || [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
const systemMessages = messages.filter((msg) => msg.role === 'system');
|
|
||||||
const nonSystemMessages = messages.filter((msg) => msg.role !== 'system');
|
|
||||||
|
|
||||||
// Combine multiple system messages if present
|
|
||||||
let systemPrompt =
|
|
||||||
systemMessages.length > 0
|
|
||||||
? systemMessages.map((msg) => msg.content).join('\n\n')
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
// Add Gemini CLI specific JSON enforcement if requested
|
|
||||||
if (options.enforceJsonOutput) {
|
|
||||||
const jsonEnforcement = this._getJsonEnforcementPrompt();
|
|
||||||
systemPrompt = systemPrompt
|
|
||||||
? `${systemPrompt}\n\n${jsonEnforcement}`
|
|
||||||
: jsonEnforcement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { systemPrompt, messages: nonSystemMessages };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a Gemini CLI specific system prompt to enforce strict JSON output
|
|
||||||
* @returns {string} JSON enforcement system prompt
|
|
||||||
*/
|
|
||||||
_getJsonEnforcementPrompt() {
|
|
||||||
return `CRITICAL: You MUST respond with ONLY valid JSON. Do not include any explanatory text, markdown formatting, code block markers, or conversational phrases like "Here is" or "Of course". Your entire response must be parseable JSON that starts with { or [ and ends with } or ]. No exceptions.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a string is valid JSON
|
|
||||||
* @param {string} text - Text to validate
|
|
||||||
* @returns {boolean} True if valid JSON
|
|
||||||
*/
|
|
||||||
_isValidJson(text) {
|
|
||||||
if (!text || typeof text !== 'string') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
JSON.parse(text.trim());
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detects if the user prompt is requesting JSON output
|
|
||||||
* @param {Array} messages - Array of message objects
|
|
||||||
* @returns {boolean} True if JSON output is likely expected
|
|
||||||
*/
|
|
||||||
_detectJsonRequest(messages) {
|
|
||||||
const userMessages = messages.filter((msg) => msg.role === 'user');
|
|
||||||
const combinedText = userMessages
|
|
||||||
.map((msg) => msg.content)
|
|
||||||
.join(' ')
|
|
||||||
.toLowerCase();
|
|
||||||
|
|
||||||
// Look for indicators that JSON output is expected
|
|
||||||
const jsonIndicators = [
|
|
||||||
'json',
|
|
||||||
'respond only with',
|
|
||||||
'return only',
|
|
||||||
'output only',
|
|
||||||
'format:',
|
|
||||||
'structure:',
|
|
||||||
'schema:',
|
|
||||||
'{"',
|
|
||||||
'[{',
|
|
||||||
'subtasks',
|
|
||||||
'array',
|
|
||||||
'object'
|
|
||||||
];
|
|
||||||
|
|
||||||
return jsonIndicators.some((indicator) => combinedText.includes(indicator));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simplifies complex prompts for gemini-cli to improve JSON output compliance
|
|
||||||
* @param {Array} messages - Array of message objects
|
|
||||||
* @returns {Array} Simplified messages array
|
|
||||||
*/
|
|
||||||
_simplifyJsonPrompts(messages) {
|
|
||||||
// First, check if this is an expand-task operation by looking at the system message
|
|
||||||
const systemMsg = messages.find((m) => m.role === 'system');
|
|
||||||
const isExpandTask =
|
|
||||||
systemMsg &&
|
|
||||||
systemMsg.content.includes(
|
|
||||||
'You are an AI assistant helping with task breakdown. Generate exactly'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isExpandTask) {
|
|
||||||
return messages; // Not an expand task, return unchanged
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract subtask count from system message
|
|
||||||
const subtaskCountMatch = systemMsg.content.match(
|
|
||||||
/Generate exactly (\d+) subtasks/
|
|
||||||
);
|
|
||||||
const subtaskCount = subtaskCountMatch ? subtaskCountMatch[1] : '10';
|
|
||||||
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`${this.name} detected expand-task operation, simplifying for ${subtaskCount} subtasks`
|
|
||||||
);
|
|
||||||
|
|
||||||
return messages.map((msg) => {
|
|
||||||
if (msg.role !== 'user') {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For expand-task user messages, create a much simpler, more direct prompt
|
|
||||||
// that doesn't depend on specific task content
|
|
||||||
const simplifiedPrompt = `Generate exactly ${subtaskCount} subtasks in the following JSON format.
|
|
||||||
|
|
||||||
CRITICAL INSTRUCTION: You must respond with ONLY valid JSON. No explanatory text, no "Here is", no "Of course", no markdown - just the JSON object.
|
|
||||||
|
|
||||||
Required JSON structure:
|
|
||||||
{
|
|
||||||
"subtasks": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"title": "Specific actionable task title",
|
|
||||||
"description": "Clear task description",
|
|
||||||
"dependencies": [],
|
|
||||||
"details": "Implementation details and guidance",
|
|
||||||
"testStrategy": "Testing approach"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
Generate ${subtaskCount} subtasks based on the original task context. Return ONLY the JSON object.`;
|
|
||||||
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`${this.name} simplified user prompt for better JSON compliance`
|
|
||||||
);
|
|
||||||
return { ...msg, content: simplifiedPrompt };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract JSON from Gemini's response using a tolerant parser.
|
|
||||||
*
|
|
||||||
* Optimized approach that progressively tries different parsing strategies:
|
|
||||||
* 1. Direct parsing after cleanup
|
|
||||||
* 2. Smart boundary detection with single-pass analysis
|
|
||||||
* 3. Limited character-by-character fallback for edge cases
|
|
||||||
*
|
|
||||||
* @param {string} text - Raw text which may contain JSON
|
|
||||||
* @returns {string} A valid JSON string if extraction succeeds, otherwise the original text
|
|
||||||
*/
|
|
||||||
extractJson(text) {
|
|
||||||
if (!text || typeof text !== 'string') {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
let content = text.trim();
|
|
||||||
|
|
||||||
// Early exit for very short content
|
|
||||||
if (content.length < 2) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip common wrappers in a single pass
|
|
||||||
content = content
|
|
||||||
// Remove markdown fences
|
|
||||||
.replace(/^.*?```(?:json)?\s*([\s\S]*?)\s*```.*$/i, '$1')
|
|
||||||
// Remove variable declarations
|
|
||||||
.replace(/^\s*(?:const|let|var)\s+\w+\s*=\s*([\s\S]*?)(?:;|\s*)$/i, '$1')
|
|
||||||
// Remove common prefixes
|
|
||||||
.replace(/^(?:Here's|The)\s+(?:the\s+)?JSON.*?[:]\s*/i, '')
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
// Find the first JSON-like structure
|
|
||||||
const firstObj = content.indexOf('{');
|
|
||||||
const firstArr = content.indexOf('[');
|
|
||||||
|
|
||||||
if (firstObj === -1 && firstArr === -1) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
const start =
|
|
||||||
firstArr === -1
|
|
||||||
? firstObj
|
|
||||||
: firstObj === -1
|
|
||||||
? firstArr
|
|
||||||
: Math.min(firstObj, firstArr);
|
|
||||||
content = content.slice(start);
|
|
||||||
|
|
||||||
// Optimized parsing function with error collection
|
|
||||||
const tryParse = (value) => {
|
|
||||||
if (!value || value.length < 2) return undefined;
|
|
||||||
|
|
||||||
const errors = [];
|
|
||||||
try {
|
|
||||||
const result = parse(value, errors, {
|
|
||||||
allowTrailingComma: true,
|
|
||||||
allowEmptyContent: false
|
|
||||||
});
|
|
||||||
if (errors.length === 0 && result !== undefined) {
|
|
||||||
return JSON.stringify(result, null, 2);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Parsing failed completely
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Try parsing the full content first
|
|
||||||
const fullParse = tryParse(content);
|
|
||||||
if (fullParse !== undefined) {
|
|
||||||
return fullParse;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Smart boundary detection - single pass with optimizations
|
|
||||||
const openChar = content[0];
|
|
||||||
const closeChar = openChar === '{' ? '}' : ']';
|
|
||||||
|
|
||||||
let depth = 0;
|
|
||||||
let inString = false;
|
|
||||||
let escapeNext = false;
|
|
||||||
let lastValidEnd = -1;
|
|
||||||
|
|
||||||
// Single-pass boundary detection with early termination
|
|
||||||
for (let i = 0; i < content.length && i < 10000; i++) {
|
|
||||||
// Limit scan for performance
|
|
||||||
const char = content[i];
|
|
||||||
|
|
||||||
if (escapeNext) {
|
|
||||||
escapeNext = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char === '\\') {
|
|
||||||
escapeNext = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char === '"') {
|
|
||||||
inString = !inString;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inString) continue;
|
|
||||||
|
|
||||||
if (char === openChar) {
|
|
||||||
depth++;
|
|
||||||
} else if (char === closeChar) {
|
|
||||||
depth--;
|
|
||||||
if (depth === 0) {
|
|
||||||
lastValidEnd = i + 1;
|
|
||||||
// Try parsing immediately on first valid boundary
|
|
||||||
const candidate = content.slice(0, lastValidEnd);
|
|
||||||
const parsed = tryParse(candidate);
|
|
||||||
if (parsed !== undefined) {
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we found valid boundaries but parsing failed, try limited fallback
|
|
||||||
if (lastValidEnd > 0) {
|
|
||||||
const maxAttempts = Math.min(5, Math.floor(lastValidEnd / 100)); // Limit attempts
|
|
||||||
for (let i = 0; i < maxAttempts; i++) {
|
|
||||||
const testEnd = Math.max(
|
|
||||||
lastValidEnd - i * 50,
|
|
||||||
Math.floor(lastValidEnd * 0.8)
|
|
||||||
);
|
|
||||||
const candidate = content.slice(0, testEnd);
|
|
||||||
const parsed = tryParse(candidate);
|
|
||||||
if (parsed !== undefined) {
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates text using Gemini CLI model
|
|
||||||
* Overrides base implementation to properly handle system messages and enforce JSON output when needed
|
|
||||||
*/
|
|
||||||
async generateText(params) {
|
|
||||||
try {
|
|
||||||
this.validateParams(params);
|
|
||||||
this.validateMessages(params.messages);
|
|
||||||
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`Generating ${this.name} text with model: ${params.modelId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Detect if JSON output is expected and enforce it for better gemini-cli compatibility
|
|
||||||
const enforceJsonOutput = this._detectJsonRequest(params.messages);
|
|
||||||
|
|
||||||
// Debug logging to understand what's happening
|
|
||||||
log('debug', `${this.name} JSON detection analysis:`, {
|
|
||||||
enforceJsonOutput,
|
|
||||||
messageCount: params.messages.length,
|
|
||||||
messages: params.messages.map((msg) => ({
|
|
||||||
role: msg.role,
|
|
||||||
contentPreview: msg.content
|
|
||||||
? msg.content.substring(0, 200) + '...'
|
|
||||||
: 'empty'
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
if (enforceJsonOutput) {
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`${this.name} detected JSON request - applying strict JSON enforcement system prompt`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For gemini-cli, simplify complex prompts before processing
|
|
||||||
let processedMessages = params.messages;
|
|
||||||
if (enforceJsonOutput) {
|
|
||||||
processedMessages = this._simplifyJsonPrompts(params.messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract system messages for separate handling with optional JSON enforcement
|
|
||||||
const { systemPrompt, messages } = this._extractSystemMessage(
|
|
||||||
processedMessages,
|
|
||||||
{ enforceJsonOutput }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Debug the final system prompt being sent
|
|
||||||
log('debug', `${this.name} final system prompt:`, {
|
|
||||||
systemPromptLength: systemPrompt ? systemPrompt.length : 0,
|
|
||||||
systemPromptPreview: systemPrompt
|
|
||||||
? systemPrompt.substring(0, 300) + '...'
|
|
||||||
: 'none',
|
|
||||||
finalMessageCount: messages.length
|
|
||||||
});
|
|
||||||
|
|
||||||
const client = await this.getClient(params);
|
|
||||||
const result = await generateText({
|
|
||||||
model: client(params.modelId),
|
|
||||||
system: systemPrompt,
|
|
||||||
messages: messages,
|
|
||||||
maxOutputTokens: params.maxTokens,
|
|
||||||
temperature: params.temperature
|
|
||||||
});
|
|
||||||
|
|
||||||
// If we detected a JSON request and gemini-cli returned conversational text,
|
|
||||||
// attempt to extract JSON from the response
|
|
||||||
let finalText = result.text;
|
|
||||||
if (enforceJsonOutput && result.text && !this._isValidJson(result.text)) {
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`${this.name} response appears conversational, attempting JSON extraction`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Log first 1000 chars of the response to see what Gemini actually returned
|
|
||||||
log('debug', `${this.name} raw response preview:`, {
|
|
||||||
responseLength: result.text.length,
|
|
||||||
responseStart: result.text.substring(0, 1000)
|
|
||||||
});
|
|
||||||
|
|
||||||
const extractedJson = this.extractJson(result.text);
|
|
||||||
if (this._isValidJson(extractedJson)) {
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`${this.name} successfully extracted JSON from conversational response`
|
|
||||||
);
|
|
||||||
finalText = extractedJson;
|
|
||||||
} else {
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`${this.name} JSON extraction failed, returning original response`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Log what extraction returned to debug why it failed
|
|
||||||
log('debug', `${this.name} extraction result preview:`, {
|
|
||||||
extractedLength: extractedJson ? extractedJson.length : 0,
|
|
||||||
extractedStart: extractedJson
|
|
||||||
? extractedJson.substring(0, 500)
|
|
||||||
: 'null'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`${this.name} generateText completed successfully for model: ${params.modelId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
text: finalText,
|
|
||||||
usage: {
|
|
||||||
inputTokens: result.usage?.promptTokens,
|
|
||||||
outputTokens: result.usage?.completionTokens,
|
|
||||||
totalTokens: result.usage?.totalTokens
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.handleError('text generation', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Streams text using Gemini CLI model
|
|
||||||
* Overrides base implementation to properly handle system messages and enforce JSON output when needed
|
|
||||||
*/
|
|
||||||
async streamText(params) {
|
|
||||||
try {
|
|
||||||
this.validateParams(params);
|
|
||||||
this.validateMessages(params.messages);
|
|
||||||
|
|
||||||
log('debug', `Streaming ${this.name} text with model: ${params.modelId}`);
|
|
||||||
|
|
||||||
// Detect if JSON output is expected and enforce it for better gemini-cli compatibility
|
|
||||||
const enforceJsonOutput = this._detectJsonRequest(params.messages);
|
|
||||||
|
|
||||||
// Debug logging to understand what's happening
|
|
||||||
log('debug', `${this.name} JSON detection analysis:`, {
|
|
||||||
enforceJsonOutput,
|
|
||||||
messageCount: params.messages.length,
|
|
||||||
messages: params.messages.map((msg) => ({
|
|
||||||
role: msg.role,
|
|
||||||
contentPreview: msg.content
|
|
||||||
? msg.content.substring(0, 200) + '...'
|
|
||||||
: 'empty'
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
if (enforceJsonOutput) {
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`${this.name} detected JSON request - applying strict JSON enforcement system prompt`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract system messages for separate handling with optional JSON enforcement
|
|
||||||
const { systemPrompt, messages } = this._extractSystemMessage(
|
|
||||||
params.messages,
|
|
||||||
{ enforceJsonOutput }
|
|
||||||
);
|
|
||||||
|
|
||||||
const client = await this.getClient(params);
|
|
||||||
const stream = await streamText({
|
|
||||||
model: client(params.modelId),
|
|
||||||
system: systemPrompt,
|
|
||||||
messages: messages,
|
|
||||||
maxOutputTokens: params.maxTokens,
|
|
||||||
temperature: params.temperature
|
|
||||||
});
|
|
||||||
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`${this.name} streamText initiated successfully for model: ${params.modelId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Note: For streaming, we can't intercept and modify the response in real-time
|
|
||||||
// The JSON extraction would need to happen on the consuming side
|
|
||||||
return stream;
|
|
||||||
} catch (error) {
|
|
||||||
this.handleError('text streaming', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a structured object using Gemini CLI model
|
|
||||||
* Overrides base implementation to handle Gemini-specific JSON formatting issues and system messages
|
|
||||||
*/
|
|
||||||
async generateObject(params) {
|
|
||||||
try {
|
|
||||||
// First try the standard generateObject from base class
|
|
||||||
return await super.generateObject(params);
|
|
||||||
} catch (error) {
|
|
||||||
// If it's a JSON parsing error, try to extract and parse JSON manually
|
|
||||||
if (error.message?.includes('JSON') || error.message?.includes('parse')) {
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`Gemini CLI generateObject failed with parsing error, attempting manual extraction`
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Validate params first
|
|
||||||
this.validateParams(params);
|
|
||||||
this.validateMessages(params.messages);
|
|
||||||
|
|
||||||
if (!params.schema) {
|
|
||||||
throw new Error('Schema is required for object generation');
|
|
||||||
}
|
|
||||||
if (!params.objectName) {
|
|
||||||
throw new Error('Object name is required for object generation');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract system messages for separate handling with JSON enforcement
|
|
||||||
const { systemPrompt, messages } = this._extractSystemMessage(
|
|
||||||
params.messages,
|
|
||||||
{ enforceJsonOutput: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Call generateObject directly with our client
|
|
||||||
const client = await this.getClient(params);
|
|
||||||
const result = await generateObject({
|
|
||||||
model: client(params.modelId),
|
|
||||||
system: systemPrompt,
|
|
||||||
messages: messages,
|
|
||||||
schema: params.schema,
|
|
||||||
mode: this.needsExplicitJsonSchema ? 'json' : 'auto',
|
|
||||||
maxOutputTokens: params.maxTokens,
|
|
||||||
temperature: params.temperature
|
|
||||||
});
|
|
||||||
|
|
||||||
// If we get rawResponse text, try to extract JSON from it
|
|
||||||
if (result.rawResponse?.text && !result.object) {
|
|
||||||
const extractedJson = this.extractJson(result.rawResponse.text);
|
|
||||||
try {
|
|
||||||
result.object = JSON.parse(extractedJson);
|
|
||||||
} catch (parseError) {
|
|
||||||
log(
|
|
||||||
'error',
|
|
||||||
`Failed to parse extracted JSON: ${parseError.message}`
|
|
||||||
);
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`Extracted JSON: ${extractedJson.substring(0, 500)}...`
|
|
||||||
);
|
|
||||||
throw new Error(
|
|
||||||
`Gemini CLI returned invalid JSON that could not be parsed: ${parseError.message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
object: result.object,
|
|
||||||
usage: {
|
|
||||||
inputTokens: result.usage?.promptTokens,
|
|
||||||
outputTokens: result.usage?.completionTokens,
|
|
||||||
totalTokens: result.usage?.totalTokens
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (retryError) {
|
|
||||||
log(
|
|
||||||
'error',
|
|
||||||
`Gemini CLI manual JSON extraction failed: ${retryError.message}`
|
|
||||||
);
|
|
||||||
// Re-throw the original error with more context
|
|
||||||
throw new Error(
|
|
||||||
`${this.name} failed to generate valid JSON object: ${error.message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For non-parsing errors, just re-throw
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getRequiredApiKeyName() {
|
getRequiredApiKeyName() {
|
||||||
return 'GEMINI_API_KEY';
|
return 'GEMINI_API_KEY';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether an API key is required.
|
||||||
|
* Gemini CLI primarily uses OAuth, so API key is optional.
|
||||||
|
* @returns {boolean} False - API key is not required
|
||||||
|
*/
|
||||||
isRequiredApiKey() {
|
isRequiredApiKey() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
339
tests/unit/ai-providers/gemini-cli-structured-output.test.js
Normal file
339
tests/unit/ai-providers/gemini-cli-structured-output.test.js
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
// Mock the AI SDK module
|
||||||
|
const mockGenerateObject = jest.fn();
|
||||||
|
const mockStreamObject = jest.fn();
|
||||||
|
const mockZodSchema = jest.fn((schema) => ({ _zodSchema: schema }));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('ai', () => ({
|
||||||
|
generateObject: mockGenerateObject,
|
||||||
|
streamObject: mockStreamObject,
|
||||||
|
zodSchema: mockZodSchema,
|
||||||
|
generateText: jest.fn(),
|
||||||
|
streamText: jest.fn(),
|
||||||
|
JSONParseError: class JSONParseError extends Error {},
|
||||||
|
NoObjectGeneratedError: class NoObjectGeneratedError extends Error {
|
||||||
|
static isInstance(error) {
|
||||||
|
return error instanceof NoObjectGeneratedError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock the gemini-cli SDK module
|
||||||
|
const mockModel = jest.fn((modelId) => ({ modelId, type: 'gemini-cli-model' }));
|
||||||
|
mockModel.languageModel = mockModel;
|
||||||
|
mockModel.chat = mockModel;
|
||||||
|
|
||||||
|
jest.unstable_mockModule('ai-sdk-provider-gemini-cli', () => ({
|
||||||
|
createGeminiProvider: jest.fn(() => mockModel)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock utilities
|
||||||
|
jest.unstable_mockModule('../../../scripts/modules/utils.js', () => ({
|
||||||
|
log: jest.fn(),
|
||||||
|
findProjectRoot: jest.fn(() => '/mock/project')
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
|
||||||
|
isProxyEnabled: jest.fn(() => false)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Import after mocking
|
||||||
|
const { GeminiCliProvider } = await import(
|
||||||
|
'../../../src/ai-providers/gemini-cli.js'
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('GeminiCliProvider Structured Output Integration', () => {
|
||||||
|
let provider;
|
||||||
|
|
||||||
|
// Sample Zod schema for testing
|
||||||
|
const testSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
priority: z.enum(['low', 'medium', 'high'])
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
provider = new GeminiCliProvider();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Reset mock implementations
|
||||||
|
mockGenerateObject.mockReset();
|
||||||
|
mockStreamObject.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateObject', () => {
|
||||||
|
it('should forward schema to AI SDK generateObject', async () => {
|
||||||
|
// Setup mock response
|
||||||
|
mockGenerateObject.mockResolvedValue({
|
||||||
|
object: {
|
||||||
|
title: 'Test Task',
|
||||||
|
description: 'A test task description',
|
||||||
|
priority: 'high'
|
||||||
|
},
|
||||||
|
usage: {
|
||||||
|
promptTokens: 100,
|
||||||
|
completionTokens: 50,
|
||||||
|
totalTokens: 150
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
modelId: 'gemini-2.5-pro',
|
||||||
|
messages: [
|
||||||
|
{ role: 'system', content: 'You are a helpful assistant.' },
|
||||||
|
{ role: 'user', content: 'Create a task for testing.' }
|
||||||
|
],
|
||||||
|
schema: testSchema,
|
||||||
|
objectName: 'task',
|
||||||
|
maxTokens: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await provider.generateObject(params);
|
||||||
|
|
||||||
|
// Verify generateObject was called
|
||||||
|
expect(mockGenerateObject).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Verify schema was passed through
|
||||||
|
const callArgs = mockGenerateObject.mock.calls[0][0];
|
||||||
|
expect(callArgs.schema).toBe(testSchema);
|
||||||
|
|
||||||
|
// Verify result is returned correctly
|
||||||
|
expect(result.object).toEqual({
|
||||||
|
title: 'Test Task',
|
||||||
|
description: 'A test task description',
|
||||||
|
priority: 'high'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use mode "auto" since needsExplicitJsonSchema is false', async () => {
|
||||||
|
mockGenerateObject.mockResolvedValue({
|
||||||
|
object: { title: 'Test', description: 'Test', priority: 'low' },
|
||||||
|
usage: { promptTokens: 10, completionTokens: 10, totalTokens: 20 }
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.generateObject({
|
||||||
|
modelId: 'gemini-2.5-flash',
|
||||||
|
messages: [{ role: 'user', content: 'Test' }],
|
||||||
|
schema: testSchema,
|
||||||
|
objectName: 'task',
|
||||||
|
maxTokens: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
const callArgs = mockGenerateObject.mock.calls[0][0];
|
||||||
|
|
||||||
|
// Mode should be 'auto' because needsExplicitJsonSchema is false
|
||||||
|
expect(callArgs.mode).toBe('auto');
|
||||||
|
|
||||||
|
// Verify the provider flag is correctly set
|
||||||
|
expect(provider.needsExplicitJsonSchema).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass schemaName and schemaDescription', async () => {
|
||||||
|
mockGenerateObject.mockResolvedValue({
|
||||||
|
object: { title: 'Test', description: 'Test', priority: 'medium' },
|
||||||
|
usage: { promptTokens: 10, completionTokens: 10, totalTokens: 20 }
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.generateObject({
|
||||||
|
modelId: 'gemini-2.5-pro',
|
||||||
|
messages: [{ role: 'user', content: 'Test' }],
|
||||||
|
schema: testSchema,
|
||||||
|
objectName: 'myCustomObject',
|
||||||
|
maxTokens: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
const callArgs = mockGenerateObject.mock.calls[0][0];
|
||||||
|
expect(callArgs.schemaName).toBe('myCustomObject');
|
||||||
|
expect(callArgs.schemaDescription).toBe(
|
||||||
|
'Generate a valid JSON object for myCustomObject'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return usage statistics from SDK response', async () => {
|
||||||
|
mockGenerateObject.mockResolvedValue({
|
||||||
|
object: { title: 'Test', description: 'Test', priority: 'high' },
|
||||||
|
usage: {
|
||||||
|
promptTokens: 250,
|
||||||
|
completionTokens: 100,
|
||||||
|
totalTokens: 350
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await provider.generateObject({
|
||||||
|
modelId: 'gemini-2.5-pro',
|
||||||
|
messages: [{ role: 'user', content: 'Test' }],
|
||||||
|
schema: testSchema,
|
||||||
|
objectName: 'task',
|
||||||
|
maxTokens: 1000
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.usage).toEqual({
|
||||||
|
inputTokens: 250,
|
||||||
|
outputTokens: 100,
|
||||||
|
totalTokens: 350
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not include temperature when supportsTemperature is false', async () => {
|
||||||
|
mockGenerateObject.mockResolvedValue({
|
||||||
|
object: { title: 'Test', description: 'Test', priority: 'low' },
|
||||||
|
usage: { promptTokens: 10, completionTokens: 10, totalTokens: 20 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// GeminiCliProvider sets supportsTemperature = false
|
||||||
|
expect(provider.supportsTemperature).toBe(false);
|
||||||
|
|
||||||
|
await provider.generateObject({
|
||||||
|
modelId: 'gemini-2.5-pro',
|
||||||
|
messages: [{ role: 'user', content: 'Test' }],
|
||||||
|
schema: testSchema,
|
||||||
|
objectName: 'task',
|
||||||
|
maxTokens: 500,
|
||||||
|
temperature: 0.7 // This should be ignored
|
||||||
|
});
|
||||||
|
|
||||||
|
const callArgs = mockGenerateObject.mock.calls[0][0];
|
||||||
|
expect(callArgs.temperature).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('streamObject', () => {
|
||||||
|
it('should forward schema to AI SDK streamObject with zodSchema wrapper', async () => {
|
||||||
|
const mockStreamResult = {
|
||||||
|
partialObjectStream: {
|
||||||
|
[Symbol.asyncIterator]: async function* () {
|
||||||
|
yield { title: 'Test' };
|
||||||
|
yield { title: 'Test', description: 'Description' };
|
||||||
|
yield {
|
||||||
|
title: 'Test',
|
||||||
|
description: 'Description',
|
||||||
|
priority: 'high'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mockStreamObject.mockResolvedValue(mockStreamResult);
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
modelId: 'gemini-2.5-pro',
|
||||||
|
messages: [{ role: 'user', content: 'Stream a task' }],
|
||||||
|
schema: testSchema,
|
||||||
|
maxTokens: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await provider.streamObject(params);
|
||||||
|
|
||||||
|
// Verify streamObject was called
|
||||||
|
expect(mockStreamObject).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Verify zodSchema wrapper was used
|
||||||
|
expect(mockZodSchema).toHaveBeenCalledWith(testSchema);
|
||||||
|
|
||||||
|
// Verify the wrapped schema was passed
|
||||||
|
const callArgs = mockStreamObject.mock.calls[0][0];
|
||||||
|
expect(callArgs.schema).toEqual({ _zodSchema: testSchema });
|
||||||
|
|
||||||
|
// Verify stream result is returned
|
||||||
|
expect(result).toBe(mockStreamResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use default mode "auto" for streamObject', async () => {
|
||||||
|
mockStreamObject.mockResolvedValue({ partialObjectStream: {} });
|
||||||
|
|
||||||
|
await provider.streamObject({
|
||||||
|
modelId: 'gemini-2.5-flash',
|
||||||
|
messages: [{ role: 'user', content: 'Test' }],
|
||||||
|
schema: testSchema,
|
||||||
|
maxTokens: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
const callArgs = mockStreamObject.mock.calls[0][0];
|
||||||
|
expect(callArgs.mode).toBe('auto');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass maxOutputTokens to streamObject', async () => {
|
||||||
|
mockStreamObject.mockResolvedValue({ partialObjectStream: {} });
|
||||||
|
|
||||||
|
await provider.streamObject({
|
||||||
|
modelId: 'gemini-2.5-pro',
|
||||||
|
messages: [{ role: 'user', content: 'Test' }],
|
||||||
|
schema: testSchema,
|
||||||
|
maxTokens: 2000
|
||||||
|
});
|
||||||
|
|
||||||
|
const callArgs = mockStreamObject.mock.calls[0][0];
|
||||||
|
expect(callArgs.maxOutputTokens).toBe(2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SDK integration', () => {
|
||||||
|
it('should create model with correct modelId', async () => {
|
||||||
|
mockGenerateObject.mockResolvedValue({
|
||||||
|
object: { title: 'Test', description: 'Test', priority: 'low' },
|
||||||
|
usage: { totalTokens: 10 }
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.generateObject({
|
||||||
|
modelId: 'gemini-2.5-pro',
|
||||||
|
messages: [{ role: 'user', content: 'Test' }],
|
||||||
|
schema: testSchema,
|
||||||
|
objectName: 'task'
|
||||||
|
});
|
||||||
|
|
||||||
|
const callArgs = mockGenerateObject.mock.calls[0][0];
|
||||||
|
// The model should be the result of calling mockModel with the modelId
|
||||||
|
expect(callArgs.model).toEqual({
|
||||||
|
modelId: 'gemini-2.5-pro',
|
||||||
|
type: 'gemini-cli-model'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with gemini-2.5-flash model', async () => {
|
||||||
|
mockGenerateObject.mockResolvedValue({
|
||||||
|
object: {
|
||||||
|
title: 'Fast',
|
||||||
|
description: 'Quick response',
|
||||||
|
priority: 'medium'
|
||||||
|
},
|
||||||
|
usage: { totalTokens: 20 }
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await provider.generateObject({
|
||||||
|
modelId: 'gemini-2.5-flash',
|
||||||
|
messages: [{ role: 'user', content: 'Quick task' }],
|
||||||
|
schema: testSchema,
|
||||||
|
objectName: 'task'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.object.title).toBe('Fast');
|
||||||
|
|
||||||
|
const callArgs = mockGenerateObject.mock.calls[0][0];
|
||||||
|
expect(callArgs.model.modelId).toBe('gemini-2.5-flash');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with gemini-3-pro-preview model', async () => {
|
||||||
|
mockGenerateObject.mockResolvedValue({
|
||||||
|
object: {
|
||||||
|
title: 'Preview',
|
||||||
|
description: 'Latest model',
|
||||||
|
priority: 'high'
|
||||||
|
},
|
||||||
|
usage: { totalTokens: 30 }
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await provider.generateObject({
|
||||||
|
modelId: 'gemini-3-pro-preview',
|
||||||
|
messages: [{ role: 'user', content: 'Test preview' }],
|
||||||
|
schema: testSchema,
|
||||||
|
objectName: 'task'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.object.title).toBe('Preview');
|
||||||
|
|
||||||
|
const callArgs = mockGenerateObject.mock.calls[0][0];
|
||||||
|
expect(callArgs.model.modelId).toBe('gemini-3-pro-preview');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
import { jest } from '@jest/globals';
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
// Mock the ai module
|
|
||||||
jest.unstable_mockModule('ai', () => ({
|
|
||||||
generateObject: jest.fn(),
|
|
||||||
generateText: jest.fn(),
|
|
||||||
streamText: jest.fn()
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock the gemini-cli SDK module
|
// Mock the gemini-cli SDK module
|
||||||
jest.unstable_mockModule('ai-sdk-provider-gemini-cli', () => ({
|
jest.unstable_mockModule('ai-sdk-provider-gemini-cli', () => ({
|
||||||
createGeminiProvider: jest.fn((options) => {
|
createGeminiProvider: jest.fn((options) => {
|
||||||
@@ -27,12 +20,13 @@ jest.unstable_mockModule('../../../src/ai-providers/base-provider.js', () => ({
|
|||||||
BaseAIProvider: class {
|
BaseAIProvider: class {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.name = 'Base Provider';
|
this.name = 'Base Provider';
|
||||||
|
this.needsExplicitJsonSchema = false;
|
||||||
|
this.supportsTemperature = true;
|
||||||
}
|
}
|
||||||
handleError(context, error) {
|
handleError(context, error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
validateParams(params) {
|
validateParams(params) {
|
||||||
// Basic validation
|
|
||||||
if (!params.modelId) {
|
if (!params.modelId) {
|
||||||
throw new Error('Model ID is required');
|
throw new Error('Model ID is required');
|
||||||
}
|
}
|
||||||
@@ -42,60 +36,54 @@ jest.unstable_mockModule('../../../src/ai-providers/base-provider.js', () => ({
|
|||||||
throw new Error('Invalid messages array');
|
throw new Error('Invalid messages array');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async generateObject(params) {
|
|
||||||
// Mock implementation that can be overridden
|
|
||||||
throw new Error('Mock base generateObject error');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock the log module
|
|
||||||
jest.unstable_mockModule('../../../scripts/modules/utils.js', () => ({
|
|
||||||
log: jest.fn()
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Import after mocking
|
// Import after mocking
|
||||||
const { GeminiCliProvider } = await import(
|
const { GeminiCliProvider } = await import(
|
||||||
'../../../src/ai-providers/gemini-cli.js'
|
'../../../src/ai-providers/gemini-cli.js'
|
||||||
);
|
);
|
||||||
const { createGeminiProvider } = await import('ai-sdk-provider-gemini-cli');
|
const { createGeminiProvider } = await import('ai-sdk-provider-gemini-cli');
|
||||||
const { generateObject, generateText, streamText } = await import('ai');
|
|
||||||
const { log } = await import('../../../scripts/modules/utils.js');
|
|
||||||
|
|
||||||
describe('GeminiCliProvider', () => {
|
describe('GeminiCliProvider', () => {
|
||||||
let provider;
|
let provider;
|
||||||
let consoleLogSpy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
provider = new GeminiCliProvider();
|
provider = new GeminiCliProvider();
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
consoleLogSpy.mockRestore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('constructor', () => {
|
describe('constructor', () => {
|
||||||
it('should set the provider name to Gemini CLI', () => {
|
it('should set the provider name to Gemini CLI', () => {
|
||||||
expect(provider.name).toBe('Gemini CLI');
|
expect(provider.name).toBe('Gemini CLI');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set supportsTemperature to false', () => {
|
||||||
|
expect(provider.supportsTemperature).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not set needsExplicitJsonSchema (rely on SDK defaults)', () => {
|
||||||
|
// The SDK has defaultObjectGenerationMode = 'json' and supportsStructuredOutputs = true
|
||||||
|
// so we don't need to override this in the provider
|
||||||
|
expect(provider.needsExplicitJsonSchema).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('validateAuth', () => {
|
describe('validateAuth', () => {
|
||||||
it('should not throw an error when API key is provided', () => {
|
it('should not throw an error when API key is provided', () => {
|
||||||
expect(() => provider.validateAuth({ apiKey: 'test-key' })).not.toThrow();
|
expect(() => provider.validateAuth({ apiKey: 'test-key' })).not.toThrow();
|
||||||
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not require API key and should not log messages', () => {
|
it('should not throw an error when no API key is provided', () => {
|
||||||
expect(() => provider.validateAuth({})).not.toThrow();
|
expect(() => provider.validateAuth({})).not.toThrow();
|
||||||
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not require any parameters', () => {
|
it('should not throw an error when called with no parameters', () => {
|
||||||
expect(() => provider.validateAuth()).not.toThrow();
|
expect(() => provider.validateAuth()).not.toThrow();
|
||||||
expect(consoleLogSpy).not.toHaveBeenCalled();
|
});
|
||||||
|
|
||||||
|
it('should not throw an error when called with undefined', () => {
|
||||||
|
expect(() => provider.validateAuth(undefined)).not.toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -121,7 +109,18 @@ describe('GeminiCliProvider', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include baseURL when provided', async () => {
|
it('should use OAuth when apiKey is the special no-key-required value', async () => {
|
||||||
|
const client = await provider.getClient({
|
||||||
|
apiKey: 'gemini-cli-no-key-required'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client).toBeDefined();
|
||||||
|
expect(createGeminiProvider).toHaveBeenCalledWith({
|
||||||
|
authType: 'oauth-personal'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include baseURL when provided with API key', async () => {
|
||||||
const client = await provider.getClient({
|
const client = await provider.getClient({
|
||||||
apiKey: 'test-key',
|
apiKey: 'test-key',
|
||||||
baseURL: 'https://custom-endpoint.com'
|
baseURL: 'https://custom-endpoint.com'
|
||||||
@@ -135,493 +134,107 @@ describe('GeminiCliProvider', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should include baseURL when provided with OAuth', async () => {
|
||||||
|
const client = await provider.getClient({
|
||||||
|
baseURL: 'https://custom-endpoint.com'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client).toBeDefined();
|
||||||
|
expect(createGeminiProvider).toHaveBeenCalledWith({
|
||||||
|
authType: 'oauth-personal',
|
||||||
|
baseURL: 'https://custom-endpoint.com'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should have languageModel and chat methods', async () => {
|
it('should have languageModel and chat methods', async () => {
|
||||||
const client = await provider.getClient({ apiKey: 'test-key' });
|
const client = await provider.getClient({ apiKey: 'test-key' });
|
||||||
expect(client.languageModel).toBeDefined();
|
expect(client.languageModel).toBeDefined();
|
||||||
expect(client.chat).toBeDefined();
|
expect(client.chat).toBeDefined();
|
||||||
expect(client.chat).toBe(client.languageModel);
|
expect(client.chat).toBe(client.languageModel);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('_extractSystemMessage', () => {
|
it('should use OAuth with empty string API key', async () => {
|
||||||
it('should extract single system message', () => {
|
await provider.getClient({ apiKey: '' });
|
||||||
const messages = [
|
|
||||||
{ role: 'system', content: 'You are a helpful assistant' },
|
|
||||||
{ role: 'user', content: 'Hello' }
|
|
||||||
];
|
|
||||||
const result = provider._extractSystemMessage(messages);
|
|
||||||
expect(result.systemPrompt).toBe('You are a helpful assistant');
|
|
||||||
expect(result.messages).toEqual([{ role: 'user', content: 'Hello' }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should combine multiple system messages', () => {
|
expect(createGeminiProvider).toHaveBeenCalledWith({
|
||||||
const messages = [
|
authType: 'oauth-personal'
|
||||||
{ role: 'system', content: 'You are helpful' },
|
|
||||||
{ role: 'system', content: 'Be concise' },
|
|
||||||
{ role: 'user', content: 'Hello' }
|
|
||||||
];
|
|
||||||
const result = provider._extractSystemMessage(messages);
|
|
||||||
expect(result.systemPrompt).toBe('You are helpful\n\nBe concise');
|
|
||||||
expect(result.messages).toEqual([{ role: 'user', content: 'Hello' }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle messages without system prompts', () => {
|
|
||||||
const messages = [
|
|
||||||
{ role: 'user', content: 'Hello' },
|
|
||||||
{ role: 'assistant', content: 'Hi there' }
|
|
||||||
];
|
|
||||||
const result = provider._extractSystemMessage(messages);
|
|
||||||
expect(result.systemPrompt).toBeUndefined();
|
|
||||||
expect(result.messages).toEqual(messages);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle empty or invalid input', () => {
|
|
||||||
expect(provider._extractSystemMessage([])).toEqual({
|
|
||||||
systemPrompt: undefined,
|
|
||||||
messages: []
|
|
||||||
});
|
|
||||||
expect(provider._extractSystemMessage(null)).toEqual({
|
|
||||||
systemPrompt: undefined,
|
|
||||||
messages: []
|
|
||||||
});
|
|
||||||
expect(provider._extractSystemMessage(undefined)).toEqual({
|
|
||||||
systemPrompt: undefined,
|
|
||||||
messages: []
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add JSON enforcement when enforceJsonOutput is true', () => {
|
it('should throw error when createGeminiProvider fails', async () => {
|
||||||
const messages = [
|
createGeminiProvider.mockImplementationOnce(() => {
|
||||||
{ role: 'system', content: 'You are a helpful assistant' },
|
throw new Error('Auth initialization failed');
|
||||||
{ role: 'user', content: 'Hello' }
|
|
||||||
];
|
|
||||||
const result = provider._extractSystemMessage(messages, {
|
|
||||||
enforceJsonOutput: true
|
|
||||||
});
|
});
|
||||||
expect(result.systemPrompt).toContain('You are a helpful assistant');
|
|
||||||
expect(result.systemPrompt).toContain(
|
await expect(provider.getClient({})).rejects.toThrow(
|
||||||
'CRITICAL: You MUST respond with ONLY valid JSON'
|
'Auth initialization failed'
|
||||||
);
|
);
|
||||||
expect(result.messages).toEqual([{ role: 'user', content: 'Hello' }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add JSON enforcement with no existing system message', () => {
|
|
||||||
const messages = [{ role: 'user', content: 'Return JSON format' }];
|
|
||||||
const result = provider._extractSystemMessage(messages, {
|
|
||||||
enforceJsonOutput: true
|
|
||||||
});
|
|
||||||
expect(result.systemPrompt).toBe(
|
|
||||||
'CRITICAL: You MUST respond with ONLY valid JSON. Do not include any explanatory text, markdown formatting, code block markers, or conversational phrases like "Here is" or "Of course". Your entire response must be parseable JSON that starts with { or [ and ends with } or ]. No exceptions.'
|
|
||||||
);
|
|
||||||
expect(result.messages).toEqual([
|
|
||||||
{ role: 'user', content: 'Return JSON format' }
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('_detectJsonRequest', () => {
|
describe('getRequiredApiKeyName', () => {
|
||||||
it('should detect JSON requests from user messages', () => {
|
it('should return GEMINI_API_KEY', () => {
|
||||||
const messages = [
|
expect(provider.getRequiredApiKeyName()).toBe('GEMINI_API_KEY');
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: 'Please return JSON format with subtasks array'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
expect(provider._detectJsonRequest(messages)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect various JSON indicators', () => {
|
|
||||||
const testCases = [
|
|
||||||
'respond only with valid JSON',
|
|
||||||
'return JSON format',
|
|
||||||
'output schema: {"test": true}',
|
|
||||||
'format: [{"id": 1}]',
|
|
||||||
'Please return subtasks in array format',
|
|
||||||
'Return an object with properties'
|
|
||||||
];
|
|
||||||
|
|
||||||
testCases.forEach((content) => {
|
|
||||||
const messages = [{ role: 'user', content }];
|
|
||||||
expect(provider._detectJsonRequest(messages)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not detect JSON requests for regular conversation', () => {
|
|
||||||
const messages = [{ role: 'user', content: 'Hello, how are you today?' }];
|
|
||||||
expect(provider._detectJsonRequest(messages)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle multiple user messages', () => {
|
|
||||||
const messages = [
|
|
||||||
{ role: 'user', content: 'Hello' },
|
|
||||||
{ role: 'assistant', content: 'Hi there' },
|
|
||||||
{ role: 'user', content: 'Now please return JSON format' }
|
|
||||||
];
|
|
||||||
expect(provider._detectJsonRequest(messages)).toBe(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('_getJsonEnforcementPrompt', () => {
|
describe('isRequiredApiKey', () => {
|
||||||
it('should return strict JSON enforcement prompt', () => {
|
it('should return false (API key is optional for gemini-cli)', () => {
|
||||||
const prompt = provider._getJsonEnforcementPrompt();
|
expect(provider.isRequiredApiKey()).toBe(false);
|
||||||
expect(prompt).toContain('CRITICAL');
|
|
||||||
expect(prompt).toContain('ONLY valid JSON');
|
|
||||||
expect(prompt).toContain('No exceptions');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('_isValidJson', () => {
|
describe('base class delegation', () => {
|
||||||
it('should return true for valid JSON objects', () => {
|
it('should not override generateText (uses base class)', () => {
|
||||||
expect(provider._isValidJson('{"test": true}')).toBe(true);
|
// Verify that generateText is not defined on the provider prototype
|
||||||
expect(provider._isValidJson('{"subtasks": [{"id": 1}]}')).toBe(true);
|
// (it inherits from base class)
|
||||||
|
expect(
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
GeminiCliProvider.prototype,
|
||||||
|
'generateText'
|
||||||
|
)
|
||||||
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true for valid JSON arrays', () => {
|
it('should not override streamText (uses base class)', () => {
|
||||||
expect(provider._isValidJson('[1, 2, 3]')).toBe(true);
|
expect(
|
||||||
expect(provider._isValidJson('[{"id": 1}, {"id": 2}]')).toBe(true);
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
GeminiCliProvider.prototype,
|
||||||
|
'streamText'
|
||||||
|
)
|
||||||
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false for invalid JSON', () => {
|
it('should not override generateObject (uses base class)', () => {
|
||||||
expect(provider._isValidJson('Of course. Here is...')).toBe(false);
|
expect(
|
||||||
expect(provider._isValidJson('{"invalid": json}')).toBe(false);
|
Object.prototype.hasOwnProperty.call(
|
||||||
expect(provider._isValidJson('not json at all')).toBe(false);
|
GeminiCliProvider.prototype,
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle edge cases', () => {
|
|
||||||
expect(provider._isValidJson('')).toBe(false);
|
|
||||||
expect(provider._isValidJson(null)).toBe(false);
|
|
||||||
expect(provider._isValidJson(undefined)).toBe(false);
|
|
||||||
expect(provider._isValidJson(' {"test": true} ')).toBe(true); // with whitespace
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('extractJson', () => {
|
|
||||||
it('should extract JSON from markdown code blocks', () => {
|
|
||||||
const input = '```json\n{"subtasks": [{"id": 1}]}\n```';
|
|
||||||
const result = provider.extractJson(input);
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed).toEqual({ subtasks: [{ id: 1 }] });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract JSON with explanatory text', () => {
|
|
||||||
const input = 'Here\'s the JSON response:\n{"subtasks": [{"id": 1}]}';
|
|
||||||
const result = provider.extractJson(input);
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed).toEqual({ subtasks: [{ id: 1 }] });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle variable declarations', () => {
|
|
||||||
const input = 'const result = {"subtasks": [{"id": 1}]};';
|
|
||||||
const result = provider.extractJson(input);
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed).toEqual({ subtasks: [{ id: 1 }] });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle trailing commas with jsonc-parser', () => {
|
|
||||||
const input = '{"subtasks": [{"id": 1,}],}';
|
|
||||||
const result = provider.extractJson(input);
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed).toEqual({ subtasks: [{ id: 1 }] });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle arrays', () => {
|
|
||||||
const input = 'The result is: [1, 2, 3]';
|
|
||||||
const result = provider.extractJson(input);
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed).toEqual([1, 2, 3]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle nested objects with proper bracket matching', () => {
|
|
||||||
const input =
|
|
||||||
'Response: {"outer": {"inner": {"value": "test"}}} extra text';
|
|
||||||
const result = provider.extractJson(input);
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed).toEqual({ outer: { inner: { value: 'test' } } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle escaped quotes in strings', () => {
|
|
||||||
const input = '{"message": "He said \\"hello\\" to me"}';
|
|
||||||
const result = provider.extractJson(input);
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed).toEqual({ message: 'He said "hello" to me' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return original text if no JSON found', () => {
|
|
||||||
const input = 'No JSON here';
|
|
||||||
expect(provider.extractJson(input)).toBe(input);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle null or non-string input', () => {
|
|
||||||
expect(provider.extractJson(null)).toBe(null);
|
|
||||||
expect(provider.extractJson(undefined)).toBe(undefined);
|
|
||||||
expect(provider.extractJson(123)).toBe(123);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle partial JSON by finding valid boundaries', () => {
|
|
||||||
const input = '{"valid": true, "partial": "incomplete';
|
|
||||||
// Should return original text since no valid JSON can be extracted
|
|
||||||
expect(provider.extractJson(input)).toBe(input);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle performance edge cases with large text', () => {
|
|
||||||
// Test with large text that has JSON at the end
|
|
||||||
const largePrefix = 'This is a very long explanation. '.repeat(1000);
|
|
||||||
const json = '{"result": "success"}';
|
|
||||||
const input = largePrefix + json;
|
|
||||||
|
|
||||||
const result = provider.extractJson(input);
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed).toEqual({ result: 'success' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle early termination for very large invalid content', () => {
|
|
||||||
// Test that it doesn't hang on very large content without JSON
|
|
||||||
const largeText = 'No JSON here. '.repeat(2000);
|
|
||||||
const result = provider.extractJson(largeText);
|
|
||||||
expect(result).toBe(largeText);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('generateObject', () => {
|
|
||||||
const mockParams = {
|
|
||||||
modelId: 'gemini-2.0-flash-exp',
|
|
||||||
apiKey: 'test-key',
|
|
||||||
messages: [{ role: 'user', content: 'Test message' }],
|
|
||||||
schema: { type: 'object', properties: {} },
|
|
||||||
objectName: 'testObject'
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle JSON parsing errors by attempting manual extraction', async () => {
|
|
||||||
// Mock the parent generateObject to throw a JSON parsing error
|
|
||||||
jest
|
|
||||||
.spyOn(
|
|
||||||
Object.getPrototypeOf(Object.getPrototypeOf(provider)),
|
|
||||||
'generateObject'
|
'generateObject'
|
||||||
)
|
)
|
||||||
.mockRejectedValueOnce(new Error('Failed to parse JSON response'));
|
).toBe(false);
|
||||||
|
|
||||||
// Mock generateObject from ai module to return text with JSON
|
|
||||||
generateObject.mockResolvedValueOnce({
|
|
||||||
rawResponse: {
|
|
||||||
text: 'Here is the JSON:\n```json\n{"subtasks": [{"id": 1}]}\n```'
|
|
||||||
},
|
|
||||||
object: null,
|
|
||||||
usage: { promptTokens: 10, completionTokens: 20, totalTokens: 30 }
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await provider.generateObject(mockParams);
|
|
||||||
|
|
||||||
expect(log).toHaveBeenCalledWith(
|
|
||||||
'debug',
|
|
||||||
expect.stringContaining('attempting manual extraction')
|
|
||||||
);
|
|
||||||
expect(generateObject).toHaveBeenCalledWith({
|
|
||||||
model: expect.objectContaining({
|
|
||||||
id: 'gemini-2.0-flash-exp',
|
|
||||||
authOptions: expect.objectContaining({
|
|
||||||
authType: 'api-key',
|
|
||||||
apiKey: 'test-key'
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
messages: mockParams.messages,
|
|
||||||
schema: mockParams.schema,
|
|
||||||
mode: 'json', // Should use json mode for Gemini
|
|
||||||
system: expect.stringContaining(
|
|
||||||
'CRITICAL: You MUST respond with ONLY valid JSON'
|
|
||||||
),
|
|
||||||
maxTokens: undefined,
|
|
||||||
temperature: undefined
|
|
||||||
});
|
|
||||||
expect(result.object).toEqual({ subtasks: [{ id: 1 }] });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw error if manual extraction also fails', async () => {
|
it('should not override streamObject (uses base class)', () => {
|
||||||
// Mock parent to throw JSON error
|
expect(
|
||||||
jest
|
Object.prototype.hasOwnProperty.call(
|
||||||
.spyOn(
|
GeminiCliProvider.prototype,
|
||||||
Object.getPrototypeOf(Object.getPrototypeOf(provider)),
|
'streamObject'
|
||||||
'generateObject'
|
|
||||||
)
|
)
|
||||||
.mockRejectedValueOnce(new Error('Failed to parse JSON'));
|
).toBe(false);
|
||||||
|
|
||||||
// Mock generateObject to return unparseable text
|
|
||||||
generateObject.mockResolvedValueOnce({
|
|
||||||
rawResponse: { text: 'Not valid JSON at all' },
|
|
||||||
object: null
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(provider.generateObject(mockParams)).rejects.toThrow(
|
|
||||||
'Gemini CLI failed to generate valid JSON object: Failed to parse JSON'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass through non-JSON errors unchanged', async () => {
|
it('should not have JSON extraction methods (removed)', () => {
|
||||||
const otherError = new Error('Network error');
|
expect(provider._extractSystemMessage).toBeUndefined();
|
||||||
jest
|
expect(provider._detectJsonRequest).toBeUndefined();
|
||||||
.spyOn(
|
expect(provider._getJsonEnforcementPrompt).toBeUndefined();
|
||||||
Object.getPrototypeOf(Object.getPrototypeOf(provider)),
|
expect(provider._isValidJson).toBeUndefined();
|
||||||
'generateObject'
|
expect(provider.extractJson).toBeUndefined();
|
||||||
)
|
expect(provider._simplifyJsonPrompts).toBeUndefined();
|
||||||
.mockRejectedValueOnce(otherError);
|
|
||||||
|
|
||||||
await expect(provider.generateObject(mockParams)).rejects.toThrow(
|
|
||||||
'Network error'
|
|
||||||
);
|
|
||||||
expect(generateObject).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle successful response from parent', async () => {
|
|
||||||
const mockResult = {
|
|
||||||
object: { test: 'data' },
|
|
||||||
usage: { inputTokens: 5, outputTokens: 10, totalTokens: 15 }
|
|
||||||
};
|
|
||||||
jest
|
|
||||||
.spyOn(
|
|
||||||
Object.getPrototypeOf(Object.getPrototypeOf(provider)),
|
|
||||||
'generateObject'
|
|
||||||
)
|
|
||||||
.mockResolvedValueOnce(mockResult);
|
|
||||||
|
|
||||||
const result = await provider.generateObject(mockParams);
|
|
||||||
expect(result).toEqual(mockResult);
|
|
||||||
expect(generateObject).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('system message support', () => {
|
|
||||||
const mockParams = {
|
|
||||||
modelId: 'gemini-2.0-flash-exp',
|
|
||||||
apiKey: 'test-key',
|
|
||||||
messages: [
|
|
||||||
{ role: 'system', content: 'You are a helpful assistant' },
|
|
||||||
{ role: 'user', content: 'Hello' }
|
|
||||||
],
|
|
||||||
maxTokens: 100,
|
|
||||||
temperature: 0.7
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('generateText with system messages', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should pass system prompt separately to AI SDK', async () => {
|
|
||||||
const { generateText } = await import('ai');
|
|
||||||
generateText.mockResolvedValueOnce({
|
|
||||||
text: 'Hello! How can I help you?',
|
|
||||||
usage: { promptTokens: 10, completionTokens: 8, totalTokens: 18 }
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await provider.generateText(mockParams);
|
|
||||||
|
|
||||||
expect(generateText).toHaveBeenCalledWith({
|
|
||||||
model: expect.objectContaining({
|
|
||||||
id: 'gemini-2.0-flash-exp'
|
|
||||||
}),
|
|
||||||
system: 'You are a helpful assistant',
|
|
||||||
messages: [{ role: 'user', content: 'Hello' }],
|
|
||||||
maxOutputTokens: 100,
|
|
||||||
temperature: 0.7
|
|
||||||
});
|
|
||||||
expect(result.text).toBe('Hello! How can I help you?');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle messages without system prompt', async () => {
|
|
||||||
const { generateText } = await import('ai');
|
|
||||||
const paramsNoSystem = {
|
|
||||||
...mockParams,
|
|
||||||
messages: [{ role: 'user', content: 'Hello' }]
|
|
||||||
};
|
|
||||||
|
|
||||||
generateText.mockResolvedValueOnce({
|
|
||||||
text: 'Hi there!',
|
|
||||||
usage: { promptTokens: 5, completionTokens: 3, totalTokens: 8 }
|
|
||||||
});
|
|
||||||
|
|
||||||
await provider.generateText(paramsNoSystem);
|
|
||||||
|
|
||||||
expect(generateText).toHaveBeenCalledWith({
|
|
||||||
model: expect.objectContaining({
|
|
||||||
id: 'gemini-2.0-flash-exp'
|
|
||||||
}),
|
|
||||||
system: undefined,
|
|
||||||
messages: [{ role: 'user', content: 'Hello' }],
|
|
||||||
maxOutputTokens: 100,
|
|
||||||
temperature: 0.7
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('streamText with system messages', () => {
|
|
||||||
it('should pass system prompt separately to AI SDK', async () => {
|
|
||||||
const { streamText } = await import('ai');
|
|
||||||
const mockStream = { stream: 'mock-stream' };
|
|
||||||
streamText.mockResolvedValueOnce(mockStream);
|
|
||||||
|
|
||||||
const result = await provider.streamText(mockParams);
|
|
||||||
|
|
||||||
expect(streamText).toHaveBeenCalledWith({
|
|
||||||
model: expect.objectContaining({
|
|
||||||
id: 'gemini-2.0-flash-exp'
|
|
||||||
}),
|
|
||||||
system: 'You are a helpful assistant',
|
|
||||||
messages: [{ role: 'user', content: 'Hello' }],
|
|
||||||
maxOutputTokens: 100,
|
|
||||||
temperature: 0.7
|
|
||||||
});
|
|
||||||
expect(result).toBe(mockStream);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('generateObject with system messages', () => {
|
|
||||||
const mockObjectParams = {
|
|
||||||
...mockParams,
|
|
||||||
schema: { type: 'object', properties: {} },
|
|
||||||
objectName: 'testObject'
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should include system prompt in fallback generateObject call', async () => {
|
|
||||||
// Mock parent to throw JSON error
|
|
||||||
jest
|
|
||||||
.spyOn(
|
|
||||||
Object.getPrototypeOf(Object.getPrototypeOf(provider)),
|
|
||||||
'generateObject'
|
|
||||||
)
|
|
||||||
.mockRejectedValueOnce(new Error('Failed to parse JSON'));
|
|
||||||
|
|
||||||
// Mock direct generateObject call
|
|
||||||
generateObject.mockResolvedValueOnce({
|
|
||||||
object: { result: 'success' },
|
|
||||||
usage: { promptTokens: 15, completionTokens: 10, totalTokens: 25 }
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await provider.generateObject(mockObjectParams);
|
|
||||||
|
|
||||||
expect(generateObject).toHaveBeenCalledWith({
|
|
||||||
model: expect.objectContaining({
|
|
||||||
id: 'gemini-2.0-flash-exp'
|
|
||||||
}),
|
|
||||||
system: expect.stringContaining('You are a helpful assistant'),
|
|
||||||
messages: [{ role: 'user', content: 'Hello' }],
|
|
||||||
schema: mockObjectParams.schema,
|
|
||||||
mode: 'json',
|
|
||||||
maxOutputTokens: 100,
|
|
||||||
temperature: 0.7
|
|
||||||
});
|
|
||||||
expect(result.object).toEqual({ result: 'success' });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Note: Error handling for module loading is tested in integration tests
|
|
||||||
// since dynamic imports are difficult to mock properly in unit tests
|
|
||||||
|
|
||||||
describe('authentication scenarios', () => {
|
describe('authentication scenarios', () => {
|
||||||
it('should use api-key auth type with API key', async () => {
|
it('should use api-key auth type with valid API key', async () => {
|
||||||
await provider.getClient({ apiKey: 'gemini-test-key' });
|
await provider.getClient({ apiKey: 'gemini-test-key' });
|
||||||
|
|
||||||
expect(createGeminiProvider).toHaveBeenCalledWith({
|
expect(createGeminiProvider).toHaveBeenCalledWith({
|
||||||
@@ -638,8 +251,8 @@ describe('GeminiCliProvider', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle empty string API key as no API key', async () => {
|
it('should prioritize OAuth over special marker API key', async () => {
|
||||||
await provider.getClient({ apiKey: '' });
|
await provider.getClient({ apiKey: 'gemini-cli-no-key-required' });
|
||||||
|
|
||||||
expect(createGeminiProvider).toHaveBeenCalledWith({
|
expect(createGeminiProvider).toHaveBeenCalledWith({
|
||||||
authType: 'oauth-personal'
|
authType: 'oauth-personal'
|
||||||
|
|||||||
Reference in New Issue
Block a user