From b03f94e109e5983d228dd5bed01b33947fa7e74c Mon Sep 17 00:00:00 2001 From: Bulat Yapparov Date: Sun, 18 Oct 2026 22:06:53 +0100 Subject: [PATCH 1/3] fix(provider): restore the built-in google provider (0.5.1) 0.5.0 (#202) narrowed the static catalog to the custom-loader providers and dropped `google` (Google AI Studio, @ai-sdk/google), so `--model google/` with only an API key failed with model-not-found. - catalog.json: add `google` with the #202 conversion and filters (non-deprecated, tool calling, text in/out, released >= 2025-06-01). 22 models incl. gemini-3.8-flash; other providers are byte-identical. - provider.ts: a `google` custom loader passes the key from GOOGLE_GENERATIVE_AI_API_KEY, GEMINI_API_KEY or GOOGLE_API_KEY, since @ai-sdk/google only reads the first. Stored auth and config apiKey still win. - Tests for the catalog entry, each env var, precedence and no-key. - Release prep: 0.5.1 (patch, model-only), CHANGELOG, RELEASING, README. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015nwvfUHdM3bgMgD9Zpz7Mj --- CHANGELOG.md | 6 + RELEASING.md | 42 ++----- bun.lock | 2 +- packages/cli/README.md | 2 +- packages/cli/package.json | 2 +- packages/cli/src/provider/catalog.json | 32 ++++++ packages/cli/src/provider/provider.ts | 14 +++ packages/cli/test/preload.ts | 1 + packages/cli/test/provider/catalog.test.ts | 1 + packages/cli/test/provider/google.test.ts | 121 +++++++++++++++++++++ 10 files changed, 187 insertions(+), 36 deletions(-) create mode 100644 packages/cli/test/provider/google.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 227c08f7..4c306ad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.5.1 (2026-10-18) + +### Fixes + +- **Google AI Studio provider restored** — `google` (`@ai-sdk/google`) is built in again, so `aictrl run --model google/gemini-3.8-flash` works with only an API key instead of failing with model-not-found. 0.5.0 left it out of the static catalog. Set `GEMINI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` or `GOOGLE_API_KEY`, or store a key with `aictrl auth login`; when several are set, a stored key wins, then `GOOGLE_GENERATIVE_AI_API_KEY`, `GEMINI_API_KEY` and `GOOGLE_API_KEY`, and `provider.google.options.apiKey` in `aictrl.json` overrides all of them. The CLI now passes the key to the SDK itself, so `GEMINI_API_KEY` and `GOOGLE_API_KEY` also work for requests; up to 0.4.8 they loaded the provider but the SDK read only `GOOGLE_GENERATIVE_AI_API_KEY`. The catalog adds 22 Google models with the 0.5.0 filters, including `gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.5-flash` and `gemini-3.1-pro-preview` (19 providers, 1376 models). Other providers' entries are unchanged. + ## 0.5.0 (2026-10-18) ### Breaking changes diff --git a/RELEASING.md b/RELEASING.md index 54482571..dc87eaa6 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -37,39 +37,15 @@ `sandbox` before `main`; verify a sandbox review persists MCP findings and a stalled stream reports a timeout rather than waiting for the job limit. -## 0.5.0 candidate - -- **Why a minor bump (0.5.0):** the release removes public surface rather than - adding models. Caret ranges on `^0.4.x` do not pick it up; consumers opt in. -- **Breaking:** - - The built-in provider set narrows to the 18 providers in - `packages/cli/src/provider/catalog.json`, shipped in the binary; models.dev - is no longer fetched or cached (#202). Other providers must be defined under - `provider` in `aictrl.json`. GitHub Copilot is removed (#193). - - Commands removed: `github`, `pr` (#192) and `acp` (#204) print a - replacement hint and exit 1; `debug lsp`, `debug snapshot` and - `models --refresh` are gone; `run --share` and `import ` fail (#195); - `run --agent plan` fails (#162). - - Subsystems and tools removed: LSP (#191), snapshots/revert/session diffs - (#184), session sharing (#195), the file watcher (#204), `question` and - plan mode (#162), `todowrite`/`todoread` (#183). - - Prompt changes: the `bash` description has no git procedures or rules, and - no default deny rules ship; callers use `permission.bash` (#161). Headless - runs no longer discover `~/.claude`, `~/.agents` or `~/.opencode` skills - (#165). - - Config keys `lsp`, `permission.lsp`, `snapshot`, `share`, `autoshare`, - `enterprise` and `watcher` still load as warned no-ops. -- **NDJSON v1 additions:** `session_error` reason `usage_limit`, code - `PROVIDER_USAGE_LIMIT` and `resetAt` (#174); `structured_output_rejected` - keyword `rejected` (#179). -- Stored sessions from 0.4.x still load and continue (covered by e2e tests in - #183, #184, #191, #195). -- Evidence (0.4.8 vs the candidate, GLM-5.3, 8 headless tasks × 3 runs, same Bun): - - pass rate 24/24 on both; no retries, hangs or `session_error`; - - uncached input tokens −25%, tool definitions −47% (36.8k → 19.4k chars), - no title request; output tokens median +5% per run; - - trivial run 558 → 389 ms, peak RSS 270 → 174 MB, binary 114 → 105 MB, - no network at startup. +## 0.5.1 candidate + +- **Why a patch bump (0.5.1):** model-only support change. It restores the + built-in `google` provider (Google AI Studio, `@ai-sdk/google`) that 0.5.0 + left out of the static catalog, so `google/` works with only + `GEMINI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` or `GOOGLE_API_KEY` set. +- The catalog gains one provider and 22 models (18 → 19 providers, 1354 → 1376 + models); every other provider's entry is byte-identical to 0.5.0. +- No CLI flags, config schema or NDJSON events change. Publication and executor promotion are separate gates. Preparing this candidate PR does not publish npm packages or deploy an executor image. diff --git a/bun.lock b/bun.lock index a64f60bf..f2a3ba0d 100644 --- a/bun.lock +++ b/bun.lock @@ -24,7 +24,7 @@ }, "packages/cli": { "name": "@aictrl/cli", - "version": "0.5.0", + "version": "0.5.1", "bin": { "aictrl": "./bin/aictrl", }, diff --git a/packages/cli/README.md b/packages/cli/README.md index 307b2be2..4d87cc1f 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -141,7 +141,7 @@ Aictrl reads config from `.aictrl/` (project) or `~/.config/aictrl/` (global). |----------|---------| | Anthropic | `ANTHROPIC_API_KEY` | | OpenAI | `OPENAI_API_KEY` | -| Google | `GOOGLE_API_KEY` | +| Google AI Studio | `GEMINI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` or `GOOGLE_API_KEY` | | OpenRouter | `OPENROUTER_API_KEY` | Use `--variant` to select provider-specific reasoning effort. For OpenAI-compatible diff --git a/packages/cli/package.json b/packages/cli/package.json index 2a3d1318..091ccec9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.5.0", + "version": "0.5.1", "name": "@aictrl/cli", "description": "Headless execution engine for AI agent skills", "type": "module", diff --git a/packages/cli/src/provider/catalog.json b/packages/cli/src/provider/catalog.json index 4820565a..b295f6c1 100644 --- a/packages/cli/src/provider/catalog.json +++ b/packages/cli/src/provider/catalog.json @@ -447,6 +447,38 @@ "duo-chat-sonnet-5-5": {"id":"duo-chat-sonnet-5-5","providerID":"gitlab","api":{"id":"duo-chat-sonnet-5-5","npm":"@gitlab/gitlab-ai-provider"},"name":"Agentic Chat (Claude Sonnet 5.5)","family":"claude-sonnet","capabilities":{"temperature":false,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":false,"image":true,"video":false,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0,"output":0,"cache":{"read":0,"write":0}},"limit":{"context":1000000,"output":64000},"status":"active","options":{},"headers":{},"release_date":"2026-09-28"} } }, + "google": { + "id": "google", + "name": "Google", + "source": "custom", + "env": ["GOOGLE_API_KEY", "GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY"], + "options": {}, + "npm": "@ai-sdk/google", + "models": { + "deep-research-max-preview-04-2026": {"id":"deep-research-max-preview-04-2026","providerID":"google","api":{"id":"deep-research-max-preview-04-2026","npm":"@ai-sdk/google"},"name":"Deep Research Max Preview (Apr-21-2026)","family":"gemini-pro","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":true,"video":false,"pdf":false},"interleaved":false},"cost":{"input":2,"output":12,"cache":{"read":0.2,"write":0},"experimentalOver200K":{"input":4,"output":18,"cache":{"read":0.4,"write":0}}},"limit":{"context":131072,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-04-21"}, + "deep-research-preview-04-2026": {"id":"deep-research-preview-04-2026","providerID":"google","api":{"id":"deep-research-preview-04-2026","npm":"@ai-sdk/google"},"name":"Deep Research Preview (Apr-21-2026)","family":"gemini-pro","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":true,"video":false,"pdf":false},"interleaved":false},"cost":{"input":2,"output":12,"cache":{"read":0.2,"write":0},"experimentalOver200K":{"input":4,"output":18,"cache":{"read":0.4,"write":0}}},"limit":{"context":131072,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-04-21"}, + "gemini-2.5-computer-use-preview-10-2025": {"id":"gemini-2.5-computer-use-preview-10-2025","providerID":"google","api":{"id":"gemini-2.5-computer-use-preview-10-2025","npm":"@ai-sdk/google"},"name":"Gemini 2.5 Computer Use Preview 10-2025","family":"gemini-pro","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":false,"image":true,"video":false,"pdf":false},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":1.25,"output":10,"cache":{"read":0,"write":0},"experimentalOver200K":{"input":2.5,"output":15,"cache":{"read":0,"write":0}}},"limit":{"context":128000,"output":64000},"status":"active","options":{},"headers":{},"release_date":"2025-10-07"}, + "gemini-2.5-flash": {"id":"gemini-2.5-flash","providerID":"google","api":{"id":"gemini-2.5-flash","npm":"@ai-sdk/google"},"name":"Gemini 2.5 Flash","family":"gemini-flash","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.3,"output":2.5,"cache":{"read":0.03,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2025-06-17"}, + "gemini-2.5-flash-lite": {"id":"gemini-2.5-flash-lite","providerID":"google","api":{"id":"gemini-2.5-flash-lite","npm":"@ai-sdk/google"},"name":"Gemini 2.5 Flash-Lite","family":"gemini-flash-lite","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.1,"output":0.4,"cache":{"read":0.01,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2025-06-17"}, + "gemini-2.5-pro": {"id":"gemini-2.5-pro","providerID":"google","api":{"id":"gemini-2.5-pro","npm":"@ai-sdk/google"},"name":"Gemini 2.5 Pro","family":"gemini-pro","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":1.25,"output":10,"cache":{"read":0.125,"write":0},"experimentalOver200K":{"input":2.5,"output":15,"cache":{"read":0.25,"write":0}}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2025-06-17"}, + "gemini-3-flash-preview": {"id":"gemini-3-flash-preview","providerID":"google","api":{"id":"gemini-3-flash-preview","npm":"@ai-sdk/google"},"name":"Gemini 3 Flash Preview","family":"gemini-flash","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.5,"output":3,"cache":{"read":0.05,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2025-12-17"}, + "gemini-3.1-flash-lite": {"id":"gemini-3.1-flash-lite","providerID":"google","api":{"id":"gemini-3.1-flash-lite","npm":"@ai-sdk/google"},"name":"Gemini 3.1 Flash Lite","family":"gemini-flash-lite","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.25,"output":1.5,"cache":{"read":0.025,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-05-07"}, + "gemini-3.1-flash-lite-image": {"id":"gemini-3.1-flash-lite-image","providerID":"google","api":{"id":"gemini-3.1-flash-lite-image","npm":"@ai-sdk/google"},"name":"Nano Banana 2 Lite","family":"gemini-flash-lite","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":false,"image":true,"video":false,"pdf":false},"output":{"text":true,"audio":false,"image":true,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.25,"output":30,"cache":{"read":0,"write":0}},"limit":{"context":65536,"output":4096},"status":"active","options":{},"headers":{},"release_date":"2026-06-30"}, + "gemini-3.1-flash-live-preview": {"id":"gemini-3.1-flash-live-preview","providerID":"google","api":{"id":"gemini-3.1-flash-live-preview","npm":"@ai-sdk/google"},"name":"Gemini 3.1 Flash Live Preview","family":"gemini-flash","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":false},"output":{"text":true,"audio":true,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.75,"output":4.5,"cache":{"read":0,"write":0}},"limit":{"context":131072,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-03-26"}, + "gemini-3.1-pro-preview": {"id":"gemini-3.1-pro-preview","providerID":"google","api":{"id":"gemini-3.1-pro-preview","npm":"@ai-sdk/google"},"name":"Gemini 3.1 Pro Preview","family":"gemini-pro","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":2,"output":12,"cache":{"read":0.2,"write":0},"experimentalOver200K":{"input":4,"output":18,"cache":{"read":0.4,"write":0}}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-02-19"}, + "gemini-3.1-pro-preview-customtools": {"id":"gemini-3.1-pro-preview-customtools","providerID":"google","api":{"id":"gemini-3.1-pro-preview-customtools","npm":"@ai-sdk/google"},"name":"Gemini 3.1 Pro Preview Custom Tools","family":"gemini-pro","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":2,"output":12,"cache":{"read":0.2,"write":0},"experimentalOver200K":{"input":4,"output":18,"cache":{"read":0.4,"write":0}}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-02-19"}, + "gemini-3.5-flash": {"id":"gemini-3.5-flash","providerID":"google","api":{"id":"gemini-3.5-flash","npm":"@ai-sdk/google"},"name":"Gemini 3.5 Flash","family":"gemini-flash","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":1.5,"output":9,"cache":{"read":0.15,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-05-19"}, + "gemini-3.5-flash-lite": {"id":"gemini-3.5-flash-lite","providerID":"google","api":{"id":"gemini-3.5-flash-lite","npm":"@ai-sdk/google"},"name":"Gemini 3.5 Flash Lite","family":"gemini-flash-lite","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.3,"output":2.5,"cache":{"read":0.03,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-07-21"}, + "gemini-3.6-flash": {"id":"gemini-3.6-flash","providerID":"google","api":{"id":"gemini-3.6-flash","npm":"@ai-sdk/google"},"name":"Gemini 3.6 Flash","family":"gemini-flash","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.75,"output":3.75,"cache":{"read":0.075,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-07-21"}, + "gemini-3.7-flash": {"id":"gemini-3.7-flash","providerID":"google","api":{"id":"gemini-3.7-flash","npm":"@ai-sdk/google"},"name":"Gemini 3.7 Flash","family":"gemini-flash","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.75,"output":3.75,"cache":{"read":0.075,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-08-13"}, + "gemini-3.8-flash": {"id":"gemini-3.8-flash","providerID":"google","api":{"id":"gemini-3.8-flash","npm":"@ai-sdk/google"},"name":"Gemini 3.8 Flash","family":"gemini-flash","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.75,"output":3.75,"cache":{"read":0.075,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-09-02"}, + "gemini-4-argon": {"id":"gemini-4-argon","providerID":"google","api":{"id":"gemini-4-argon","npm":"@ai-sdk/google"},"name":"Gemini 4 Argon","family":"gemini","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":2,"output":10,"cache":{"read":0.1,"write":0}},"limit":{"context":1048576,"output":32768},"status":"active","options":{},"headers":{},"release_date":"2026-10-13"}, + "gemini-flash-latest": {"id":"gemini-flash-latest","providerID":"google","api":{"id":"gemini-flash-latest","npm":"@ai-sdk/google"},"name":"Gemini Flash Latest","family":"gemini-flash","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.75,"output":3.75,"cache":{"read":0.075,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-08-13"}, + "gemini-flash-lite-latest": {"id":"gemini-flash-lite-latest","providerID":"google","api":{"id":"gemini-flash-lite-latest","npm":"@ai-sdk/google"},"name":"Gemini Flash-Lite Latest","family":"gemini-flash-lite","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":true,"image":true,"video":true,"pdf":true},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0.3,"output":2.5,"cache":{"read":0.03,"write":0}},"limit":{"context":1048576,"output":65536},"status":"active","options":{},"headers":{},"release_date":"2026-07-21"}, + "gemma-4-26b-a4b-it": {"id":"gemma-4-26b-a4b-it","providerID":"google","api":{"id":"gemma-4-26b-a4b-it","npm":"@ai-sdk/google"},"name":"Gemma 4 26B A4B IT","family":"gemma","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":false,"image":true,"video":false,"pdf":false},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0,"output":0,"cache":{"read":0,"write":0}},"limit":{"context":262144,"output":32768},"status":"active","options":{},"headers":{},"release_date":"2026-04-02"}, + "gemma-4-31b-it": {"id":"gemma-4-31b-it","providerID":"google","api":{"id":"gemma-4-31b-it","npm":"@ai-sdk/google"},"name":"Gemma 4 31B IT","family":"gemma","capabilities":{"temperature":true,"reasoning":true,"attachment":true,"toolcall":true,"input":{"text":true,"audio":false,"image":true,"video":false,"pdf":false},"output":{"text":true,"audio":false,"image":false,"video":false,"pdf":false},"interleaved":false},"cost":{"input":0,"output":0,"cache":{"read":0,"write":0}},"limit":{"context":262144,"output":32768},"status":"active","options":{},"headers":{},"release_date":"2026-04-02"} + } + }, "google-vertex": { "id": "google-vertex", "name": "Vertex", diff --git a/packages/cli/src/provider/provider.ts b/packages/cli/src/provider/provider.ts index 34e6bea5..a63d672b 100644 --- a/packages/cli/src/provider/provider.ts +++ b/packages/cli/src/provider/provider.ts @@ -25,6 +25,14 @@ import { Installation } from "../installation" export namespace Provider { const log = Log.create({ service: "provider" }) + // @ai-sdk/google only reads GOOGLE_GENERATIVE_AI_API_KEY. The other names are the Gemini API's own + // (GEMINI_API_KEY, then the generic GOOGLE_API_KEY), so the loader passes their value explicitly. + export const GOOGLE_AI_STUDIO_KEY_ENV = ["GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY"] + + function googleAIStudioKey() { + return GOOGLE_AI_STUDIO_KEY_ENV.map((name) => Env.get(name)).find(Boolean) + } + // A DNS label can have 63 characters; regional hosts append "-aiplatform". const MAX_VERTEX_LOCATION_LENGTH = 63 - "-aiplatform".length @@ -334,6 +342,12 @@ export namespace Provider { }, } }, + google: async (provider) => { + // A key stored with `auth login` reaches the SDK as provider.key and wins over env vars. + if ((await Auth.get(provider.id))?.type === "api") return { autoload: false } + const apiKey = googleAIStudioKey() + return { autoload: false, options: apiKey ? { apiKey } : {} } + }, "google-vertex": async (provider) => { const project = googleVertexProject(provider.options ?? {}) diff --git a/packages/cli/test/preload.ts b/packages/cli/test/preload.ts index aaea0ea4..9eec2fd5 100644 --- a/packages/cli/test/preload.ts +++ b/packages/cli/test/preload.ts @@ -53,6 +53,7 @@ delete process.env["ANTHROPIC_API_KEY"] delete process.env["OPENAI_API_KEY"] delete process.env["GOOGLE_API_KEY"] delete process.env["GOOGLE_GENERATIVE_AI_API_KEY"] +delete process.env["GEMINI_API_KEY"] delete process.env["AZURE_OPENAI_API_KEY"] delete process.env["AWS_ACCESS_KEY_ID"] delete process.env["AWS_PROFILE"] diff --git a/packages/cli/test/provider/catalog.test.ts b/packages/cli/test/provider/catalog.test.ts index 58a72f8d..425f445c 100644 --- a/packages/cli/test/provider/catalog.test.ts +++ b/packages/cli/test/provider/catalog.test.ts @@ -17,6 +17,7 @@ const BUILT_IN_PROVIDERS = [ "cloudflare-ai-gateway", "cloudflare-workers-ai", "gitlab", + "google", "google-vertex", "google-vertex-anthropic", "kilo", diff --git a/packages/cli/test/provider/google.test.ts b/packages/cli/test/provider/google.test.ts new file mode 100644 index 00000000..2f625e0a --- /dev/null +++ b/packages/cli/test/provider/google.test.ts @@ -0,0 +1,121 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test" +import { tmpdir } from "../fixture/fixture" +import { Instance } from "../../src/project/instance" +import { Provider } from "../../src/provider/provider" +import { ModelCatalog } from "../../src/provider/catalog" +import { Auth } from "../../src/auth" +import { Env } from "../../src/env" + +const KEY_ENV = ["GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY"] + +// A fake Gemini API endpoint that records the API key each request carries. +let server: ReturnType +let keys: (string | null)[] + +beforeEach(() => { + keys = [] + server = Bun.serve({ + port: 0, + fetch(request) { + keys.push(request.headers.get("x-goog-api-key")) + return Response.json({ + candidates: [{ content: { role: "model", parts: [{ text: "done" }] }, finishReason: "STOP" }], + usageMetadata: { promptTokenCount: 1, candidatesTokenCount: 1, totalTokenCount: 2 }, + }) + }, + }) +}) + +afterEach(async () => { + server.stop(true) + await Auth.remove("google") +}) + +function withGoogle(input: { env: Record; options?: Record }, fn: () => Promise) { + return (async () => { + await using tmp = await tmpdir({ + config: { provider: { google: { options: { baseURL: `${server.url.origin}/v1beta`, ...input.options } } } }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + for (const name of KEY_ENV) Env.remove(name) + for (const [name, value] of Object.entries(input.env)) Env.set(name, value) + }, + fn, + }) + })() +} + +async function sentKey() { + const model = await Provider.getModel("google", "gemini-3.8-flash") + const language = await Provider.getLanguage(model) + await language.doGenerate({ prompt: [{ role: "user", content: [{ type: "text", text: "Say done." }] }] }) + return keys.at(-1) +} + +describe("google provider", () => { + test("is built in with the Google AI Studio SDK and current Gemini models", () => { + const google = ModelCatalog.providers()["google"] + expect(google.name).toBe("Google") + expect([...google.env].sort()).toEqual([...KEY_ENV].sort()) + expect([...Provider.GOOGLE_AI_STUDIO_KEY_ENV].sort()).toEqual([...KEY_ENV].sort()) + expect(ModelCatalog.sdkDefaults("google")).toEqual({ npm: "@ai-sdk/google", api: undefined }) + + for (const id of ["gemini-3.8-flash", "gemini-3.7-flash", "gemini-3.5-flash", "gemini-3.1-pro-preview"]) { + const model = google.models[id] + expect(model, id).toBeDefined() + expect(model.api.id).toBe(id) + expect(model.api.npm).toBe("@ai-sdk/google") + // The SDK knows its own endpoint, so the catalog carries no base URL. + expect(model.api.url).toBeUndefined() + expect(model.limit).toEqual({ context: 1_048_576, output: 65_536 }) + } + }) + + test.each(KEY_ENV)("resolves models and sends the key with only %s set", async (name) => { + await withGoogle({ env: { [name]: `key-from-${name}` } }, async () => { + const providers = await Provider.list() + expect(providers["google"].models["gemini-3.8-flash"]).toBeDefined() + expect(await sentKey()).toBe(`key-from-${name}`) + }) + }) + + test("prefers the SDK's own env var, then GEMINI_API_KEY, over GOOGLE_API_KEY", async () => { + await withGoogle({ env: { GOOGLE_API_KEY: "generic", GEMINI_API_KEY: "gemini" } }, async () => { + expect(await sentKey()).toBe("gemini") + }) + await withGoogle( + { env: { GOOGLE_API_KEY: "generic", GEMINI_API_KEY: "gemini", GOOGLE_GENERATIVE_AI_API_KEY: "sdk" } }, + async () => { + expect(await sentKey()).toBe("sdk") + }, + ) + }) + + test("a key stored with auth login wins over env vars", async () => { + await Auth.set("google", { type: "api", key: "stored" }) + await withGoogle({ env: { GEMINI_API_KEY: "gemini" } }, async () => { + expect(await sentKey()).toBe("stored") + }) + }) + + test("a config apiKey wins over env vars", async () => { + await withGoogle({ env: { GEMINI_API_KEY: "gemini" }, options: { apiKey: "configured" } }, async () => { + expect(await sentKey()).toBe("configured") + }) + }) + + test("is not loaded without a key", async () => { + await using tmp = await tmpdir({ config: {} }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + for (const name of KEY_ENV) Env.remove(name) + }, + fn: async () => { + expect((await Provider.list())["google"]).toBeUndefined() + }, + }) + }) +}) From 8297e57651bcf51cab46d4d5f82f0256666219e9 Mon Sep 17 00:00:00 2001 From: Bulat Yapparov Date: Sun, 18 Oct 2026 22:18:37 +0100 Subject: [PATCH 2/3] fix(provider): keep google key precedence in the catalog env (#216 review) - catalog `google.env` lists the keys in precedence order and the loader reads it, so there is one ordered list (and a config `env` override is honoured). The test compares it unsorted. - README lists the keys in precedence order. - CHANGELOG/RELEASING: a 0.5.0 config `provider.google` now extends the built-in (models without npm use @ai-sdk/google, as up to 0.4.8); document the npm + whitelist opt-out and pin both with tests. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015nwvfUHdM3bgMgD9Zpz7Mj --- CHANGELOG.md | 2 +- RELEASING.md | 6 +++- packages/cli/README.md | 2 +- packages/cli/src/provider/catalog.json | 2 +- packages/cli/src/provider/provider.ts | 12 ++----- packages/cli/test/provider/google.test.ts | 41 +++++++++++++++++++++-- 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c306ad7..748af79b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixes -- **Google AI Studio provider restored** — `google` (`@ai-sdk/google`) is built in again, so `aictrl run --model google/gemini-3.8-flash` works with only an API key instead of failing with model-not-found. 0.5.0 left it out of the static catalog. Set `GEMINI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` or `GOOGLE_API_KEY`, or store a key with `aictrl auth login`; when several are set, a stored key wins, then `GOOGLE_GENERATIVE_AI_API_KEY`, `GEMINI_API_KEY` and `GOOGLE_API_KEY`, and `provider.google.options.apiKey` in `aictrl.json` overrides all of them. The CLI now passes the key to the SDK itself, so `GEMINI_API_KEY` and `GOOGLE_API_KEY` also work for requests; up to 0.4.8 they loaded the provider but the SDK read only `GOOGLE_GENERATIVE_AI_API_KEY`. The catalog adds 22 Google models with the 0.5.0 filters, including `gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.5-flash` and `gemini-3.1-pro-preview` (19 providers, 1376 models). Other providers' entries are unchanged. +- **Google AI Studio provider restored** — `google` (`@ai-sdk/google`) is built in again, so `aictrl run --model google/gemini-3.8-flash` works with only an API key instead of failing with model-not-found. 0.5.0 left it out of the static catalog. Set `GEMINI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` or `GOOGLE_API_KEY`, or store a key with `aictrl auth login`; when several are set, a stored key wins, then `GOOGLE_GENERATIVE_AI_API_KEY`, `GEMINI_API_KEY` and `GOOGLE_API_KEY`, and `provider.google.options.apiKey` in `aictrl.json` overrides all of them. The CLI now passes the key to the SDK itself, so `GEMINI_API_KEY` and `GOOGLE_API_KEY` also work for requests; up to 0.4.8 they loaded the provider but the SDK read only `GOOGLE_GENERATIVE_AI_API_KEY`. The catalog adds 22 Google models with the 0.5.0 filters, including `gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.5-flash` and `gemini-3.1-pro-preview` (19 providers, 1376 models). Other providers' entries are unchanged. If you defined your own `provider.google` in `aictrl.json` on 0.5.0, it now extends the built-in: its models without an `npm` use `@ai-sdk/google` again (as up to 0.4.8) instead of `@ai-sdk/openai-compatible`, and the built-in Google models are listed next to yours. To keep an OpenAI-compatible endpoint under that id, set `"npm": "@ai-sdk/openai-compatible"` and a `whitelist` of your models, or give the provider another id. ## 0.5.0 (2026-10-18) diff --git a/RELEASING.md b/RELEASING.md index dc87eaa6..6ddfcddf 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -45,7 +45,11 @@ `GEMINI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` or `GOOGLE_API_KEY` set. - The catalog gains one provider and 22 models (18 → 19 providers, 1354 → 1376 models); every other provider's entry is byte-identical to 0.5.0. -- No CLI flags, config schema or NDJSON events change. +- No CLI flags, config schema or NDJSON events change. One config behaviour + changes: a `provider.google` defined in `aictrl.json` on 0.5.0 now extends + the built-in, so its models without `npm` use `@ai-sdk/google` again (as up + to 0.4.8) and the built-in models are listed too. The CHANGELOG gives the + `npm` + `whitelist` opt-out. Publication and executor promotion are separate gates. Preparing this candidate PR does not publish npm packages or deploy an executor image. diff --git a/packages/cli/README.md b/packages/cli/README.md index 4d87cc1f..b413090e 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -141,7 +141,7 @@ Aictrl reads config from `.aictrl/` (project) or `~/.config/aictrl/` (global). |----------|---------| | Anthropic | `ANTHROPIC_API_KEY` | | OpenAI | `OPENAI_API_KEY` | -| Google AI Studio | `GEMINI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` or `GOOGLE_API_KEY` | +| Google AI Studio | `GOOGLE_GENERATIVE_AI_API_KEY`, `GEMINI_API_KEY` or `GOOGLE_API_KEY` | | OpenRouter | `OPENROUTER_API_KEY` | Use `--variant` to select provider-specific reasoning effort. For OpenAI-compatible diff --git a/packages/cli/src/provider/catalog.json b/packages/cli/src/provider/catalog.json index b295f6c1..e7084ee7 100644 --- a/packages/cli/src/provider/catalog.json +++ b/packages/cli/src/provider/catalog.json @@ -451,7 +451,7 @@ "id": "google", "name": "Google", "source": "custom", - "env": ["GOOGLE_API_KEY", "GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY"], + "env": ["GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY"], "options": {}, "npm": "@ai-sdk/google", "models": { diff --git a/packages/cli/src/provider/provider.ts b/packages/cli/src/provider/provider.ts index a63d672b..41f14826 100644 --- a/packages/cli/src/provider/provider.ts +++ b/packages/cli/src/provider/provider.ts @@ -25,14 +25,6 @@ import { Installation } from "../installation" export namespace Provider { const log = Log.create({ service: "provider" }) - // @ai-sdk/google only reads GOOGLE_GENERATIVE_AI_API_KEY. The other names are the Gemini API's own - // (GEMINI_API_KEY, then the generic GOOGLE_API_KEY), so the loader passes their value explicitly. - export const GOOGLE_AI_STUDIO_KEY_ENV = ["GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY"] - - function googleAIStudioKey() { - return GOOGLE_AI_STUDIO_KEY_ENV.map((name) => Env.get(name)).find(Boolean) - } - // A DNS label can have 63 characters; regional hosts append "-aiplatform". const MAX_VERTEX_LOCATION_LENGTH = 63 - "-aiplatform".length @@ -345,7 +337,9 @@ export namespace Provider { google: async (provider) => { // A key stored with `auth login` reaches the SDK as provider.key and wins over env vars. if ((await Auth.get(provider.id))?.type === "api") return { autoload: false } - const apiKey = googleAIStudioKey() + // @ai-sdk/google reads only GOOGLE_GENERATIVE_AI_API_KEY, and the generic env loader passes no + // key for providers with several env vars, so pass the first one set, in catalog `env` order. + const apiKey = provider.env.map((name) => Env.get(name)).find(Boolean) return { autoload: false, options: apiKey ? { apiKey } : {} } }, "google-vertex": async (provider) => { diff --git a/packages/cli/test/provider/google.test.ts b/packages/cli/test/provider/google.test.ts index 2f625e0a..e9828e73 100644 --- a/packages/cli/test/provider/google.test.ts +++ b/packages/cli/test/provider/google.test.ts @@ -6,6 +6,7 @@ import { ModelCatalog } from "../../src/provider/catalog" import { Auth } from "../../src/auth" import { Env } from "../../src/env" +// In precedence order: the loader passes the first one set. const KEY_ENV = ["GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY"] // A fake Gemini API endpoint that records the API key each request carries. @@ -58,8 +59,7 @@ describe("google provider", () => { test("is built in with the Google AI Studio SDK and current Gemini models", () => { const google = ModelCatalog.providers()["google"] expect(google.name).toBe("Google") - expect([...google.env].sort()).toEqual([...KEY_ENV].sort()) - expect([...Provider.GOOGLE_AI_STUDIO_KEY_ENV].sort()).toEqual([...KEY_ENV].sort()) + expect(google.env).toEqual(KEY_ENV) expect(ModelCatalog.sdkDefaults("google")).toEqual({ npm: "@ai-sdk/google", api: undefined }) for (const id of ["gemini-3.8-flash", "gemini-3.7-flash", "gemini-3.5-flash", "gemini-3.1-pro-preview"]) { @@ -106,6 +106,43 @@ describe("google provider", () => { }) }) + test("a config model on google without npm uses @ai-sdk/google, as up to 0.4.8", async () => { + await using tmp = await tmpdir({ + config: { provider: { google: { options: { apiKey: "k" }, models: { "my-gemini": {} } } } }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const google = (await Provider.list())["google"] + expect(google.models["my-gemini"].api.npm).toBe("@ai-sdk/google") + expect(google.models["gemini-3.8-flash"]).toBeDefined() + }, + }) + }) + + test("an OpenAI-compatible provider.google keeps its SDK and models with npm and whitelist", async () => { + await using tmp = await tmpdir({ + config: { + provider: { + google: { + npm: "@ai-sdk/openai-compatible", + options: { baseURL: "https://proxy.example/v1", apiKey: "k" }, + models: { "my-gemini": {} }, + whitelist: ["my-gemini"], + }, + }, + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const google = (await Provider.list())["google"] + expect(Object.keys(google.models)).toEqual(["my-gemini"]) + expect(google.models["my-gemini"].api.npm).toBe("@ai-sdk/openai-compatible") + }, + }) + }) + test("is not loaded without a key", async () => { await using tmp = await tmpdir({ config: {} }) await Instance.provide({ From 5ae4ed112a290aa61227e7b3ce0426df7c34a179 Mon Sep 17 00:00:00 2001 From: Bulat Yapparov Date: Sun, 18 Oct 2026 22:29:33 +0100 Subject: [PATCH 3/3] fix(provider): pass the Google env key only to @ai-sdk/google models (#216 review 2) - The google loader skips env-key injection when any google model resolves to another SDK, so a config `provider.google` pointed at an OpenAI-compatible proxy never receives GEMINI_API_KEY & co. Covered by a regression test that fails without the guard. - CHANGELOG/RELEASING list the env vars in precedence order and note that such configs set their own options.apiKey. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015nwvfUHdM3bgMgD9Zpz7Mj --- CHANGELOG.md | 2 +- RELEASING.md | 2 +- packages/cli/src/provider/provider.ts | 4 ++++ packages/cli/test/provider/google.test.ts | 28 +++++++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 748af79b..08832409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixes -- **Google AI Studio provider restored** — `google` (`@ai-sdk/google`) is built in again, so `aictrl run --model google/gemini-3.8-flash` works with only an API key instead of failing with model-not-found. 0.5.0 left it out of the static catalog. Set `GEMINI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` or `GOOGLE_API_KEY`, or store a key with `aictrl auth login`; when several are set, a stored key wins, then `GOOGLE_GENERATIVE_AI_API_KEY`, `GEMINI_API_KEY` and `GOOGLE_API_KEY`, and `provider.google.options.apiKey` in `aictrl.json` overrides all of them. The CLI now passes the key to the SDK itself, so `GEMINI_API_KEY` and `GOOGLE_API_KEY` also work for requests; up to 0.4.8 they loaded the provider but the SDK read only `GOOGLE_GENERATIVE_AI_API_KEY`. The catalog adds 22 Google models with the 0.5.0 filters, including `gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.5-flash` and `gemini-3.1-pro-preview` (19 providers, 1376 models). Other providers' entries are unchanged. If you defined your own `provider.google` in `aictrl.json` on 0.5.0, it now extends the built-in: its models without an `npm` use `@ai-sdk/google` again (as up to 0.4.8) instead of `@ai-sdk/openai-compatible`, and the built-in Google models are listed next to yours. To keep an OpenAI-compatible endpoint under that id, set `"npm": "@ai-sdk/openai-compatible"` and a `whitelist` of your models, or give the provider another id. +- **Google AI Studio provider restored** — `google` (`@ai-sdk/google`) is built in again, so `aictrl run --model google/gemini-3.8-flash` works with only an API key instead of failing with model-not-found. 0.5.0 left it out of the static catalog. Set `GOOGLE_GENERATIVE_AI_API_KEY`, `GEMINI_API_KEY` or `GOOGLE_API_KEY`, or store a key with `aictrl auth login`; when several are set, a stored key wins, then `GOOGLE_GENERATIVE_AI_API_KEY`, `GEMINI_API_KEY` and `GOOGLE_API_KEY`, and `provider.google.options.apiKey` in `aictrl.json` overrides all of them. The CLI now passes the key to the SDK itself, so `GEMINI_API_KEY` and `GOOGLE_API_KEY` also work for requests; up to 0.4.8 they loaded the provider but the SDK read only `GOOGLE_GENERATIVE_AI_API_KEY`. The catalog adds 22 Google models with the 0.5.0 filters, including `gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.5-flash` and `gemini-3.1-pro-preview` (19 providers, 1376 models). Other providers' entries are unchanged. If you defined your own `provider.google` in `aictrl.json` on 0.5.0, it now extends the built-in: its models without an `npm` use `@ai-sdk/google` again (as up to 0.4.8) instead of `@ai-sdk/openai-compatible`, and the built-in Google models are listed next to yours. To keep an OpenAI-compatible endpoint under that id, set `"npm": "@ai-sdk/openai-compatible"` and a `whitelist` of your models, or give the provider another id. When any `google` model uses another SDK, the CLI does not pass the Google env key, so set `options.apiKey` for that endpoint. ## 0.5.0 (2026-10-18) diff --git a/RELEASING.md b/RELEASING.md index 6ddfcddf..d653da7a 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -42,7 +42,7 @@ - **Why a patch bump (0.5.1):** model-only support change. It restores the built-in `google` provider (Google AI Studio, `@ai-sdk/google`) that 0.5.0 left out of the static catalog, so `google/` works with only - `GEMINI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` or `GOOGLE_API_KEY` set. + `GOOGLE_GENERATIVE_AI_API_KEY`, `GEMINI_API_KEY` or `GOOGLE_API_KEY` set. - The catalog gains one provider and 22 models (18 → 19 providers, 1354 → 1376 models); every other provider's entry is byte-identical to 0.5.0. - No CLI flags, config schema or NDJSON events change. One config behaviour diff --git a/packages/cli/src/provider/provider.ts b/packages/cli/src/provider/provider.ts index 41f14826..a458374b 100644 --- a/packages/cli/src/provider/provider.ts +++ b/packages/cli/src/provider/provider.ts @@ -337,6 +337,10 @@ export namespace Provider { google: async (provider) => { // A key stored with `auth login` reaches the SDK as provider.key and wins over env vars. if ((await Auth.get(provider.id))?.type === "api") return { autoload: false } + // A config that points `google` models at another SDK (e.g. an OpenAI-compatible proxy) must not + // receive the Google key. + if (!Object.values(provider.models).every((model) => model.api.npm === "@ai-sdk/google")) + return { autoload: false } // @ai-sdk/google reads only GOOGLE_GENERATIVE_AI_API_KEY, and the generic env loader passes no // key for providers with several env vars, so pass the first one set, in catalog `env` order. const apiKey = provider.env.map((name) => Env.get(name)).find(Boolean) diff --git a/packages/cli/test/provider/google.test.ts b/packages/cli/test/provider/google.test.ts index e9828e73..fce9a965 100644 --- a/packages/cli/test/provider/google.test.ts +++ b/packages/cli/test/provider/google.test.ts @@ -143,6 +143,34 @@ describe("google provider", () => { }) }) + test("does not pass the Google env key to a provider.google that uses another SDK", async () => { + await using tmp = await tmpdir({ + config: { + provider: { + google: { + npm: "@ai-sdk/openai-compatible", + options: { baseURL: "https://proxy.example/v1" }, + models: { "my-gemini": {} }, + whitelist: ["my-gemini"], + }, + }, + }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + for (const name of KEY_ENV) Env.remove(name) + Env.set("GEMINI_API_KEY", "google-secret") + }, + fn: async () => { + const google = (await Provider.list())["google"] + expect(google.models["my-gemini"].api.npm).toBe("@ai-sdk/openai-compatible") + expect(google.options.apiKey).toBeUndefined() + expect(google.key).toBeUndefined() + }, + }) + }) + test("is not loaded without a key", async () => { await using tmp = await tmpdir({ config: {} }) await Instance.provide({