diff --git a/CHANGELOG.md b/CHANGELOG.md index 16c601359..508717c71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - Signal: accept UUID-only senders for pairing/allowlists/routing when sourceNumber is missing. (#523) — thanks @neist - Agent system prompt: avoid automatic self-updates unless explicitly requested. - Onboarding: tighten QuickStart hint copy for configuring later. +- Onboarding: set Gemini 3 Pro as the default model for Gemini API key auth. (#489) — thanks @jonasjancarik - Onboarding: avoid “token expired” for Codex CLI when expiry is heuristic. - Onboarding: QuickStart jumps straight into provider selection with Telegram preselected when unset. - Onboarding: QuickStart auto-installs the Gateway daemon with Node (no runtime picker). diff --git a/src/commands/auth-choice.ts b/src/commands/auth-choice.ts index 9407be5d9..42eba1ad3 100644 --- a/src/commands/auth-choice.ts +++ b/src/commands/auth-choice.ts @@ -25,6 +25,10 @@ import { isRemoteEnvironment, loginAntigravityVpsAware, } from "./antigravity-oauth.js"; +import { + applyGoogleGeminiModelDefault, + GOOGLE_GEMINI_DEFAULT_MODEL, +} from "./google-gemini-model-default.js"; import { applyAuthProfileConfig, applyMinimaxConfig, @@ -427,6 +431,19 @@ export async function applyAuthChoice(params: { provider: "google", mode: "api_key", }); + if (params.setDefaultModel) { + const applied = applyGoogleGeminiModelDefault(nextConfig); + nextConfig = applied.next; + if (applied.changed) { + await params.prompter.note( + `Default model set to ${GOOGLE_GEMINI_DEFAULT_MODEL}`, + "Model configured", + ); + } + } else { + agentModelOverride = GOOGLE_GEMINI_DEFAULT_MODEL; + await noteAgentModel(GOOGLE_GEMINI_DEFAULT_MODEL); + } } else if (params.authChoice === "apiKey") { const key = await params.prompter.text({ message: "Enter Anthropic API key", diff --git a/src/commands/configure.ts b/src/commands/configure.ts index 3c747f316..08bf0598a 100644 --- a/src/commands/configure.ts +++ b/src/commands/configure.ts @@ -50,6 +50,10 @@ import { GATEWAY_DAEMON_RUNTIME_OPTIONS, type GatewayDaemonRuntime, } from "./daemon-runtime.js"; +import { + applyGoogleGeminiModelDefault, + GOOGLE_GEMINI_DEFAULT_MODEL, +} from "./google-gemini-model-default.js"; import { healthCommand } from "./health.js"; import { applyAuthProfileConfig, @@ -529,6 +533,14 @@ async function promptAuthConfig( provider: "google", mode: "api_key", }); + const applied = applyGoogleGeminiModelDefault(next); + next = applied.next; + if (applied.changed) { + note( + `Default model set to ${GOOGLE_GEMINI_DEFAULT_MODEL}`, + "Model configured", + ); + } } else if (authChoice === "apiKey") { const key = guardCancel( await text({ diff --git a/src/commands/google-gemini-model-default.test.ts b/src/commands/google-gemini-model-default.test.ts new file mode 100644 index 000000000..9dff42e8c --- /dev/null +++ b/src/commands/google-gemini-model-default.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; + +import type { ClawdbotConfig } from "../config/config.js"; +import { + applyGoogleGeminiModelDefault, + GOOGLE_GEMINI_DEFAULT_MODEL, +} from "./google-gemini-model-default.js"; + +describe("applyGoogleGeminiModelDefault", () => { + it("sets gemini default when model is unset", () => { + const cfg: ClawdbotConfig = { agent: {} }; + const applied = applyGoogleGeminiModelDefault(cfg); + expect(applied.changed).toBe(true); + expect(applied.next.agent?.model).toEqual({ + primary: GOOGLE_GEMINI_DEFAULT_MODEL, + }); + }); + + it("overrides existing model", () => { + const cfg: ClawdbotConfig = { + agent: { model: "anthropic/claude-opus-4-5" }, + }; + const applied = applyGoogleGeminiModelDefault(cfg); + expect(applied.changed).toBe(true); + expect(applied.next.agent?.model).toEqual({ + primary: GOOGLE_GEMINI_DEFAULT_MODEL, + }); + }); + + it("no-ops when already gemini default", () => { + const cfg: ClawdbotConfig = { + agent: { model: GOOGLE_GEMINI_DEFAULT_MODEL }, + }; + const applied = applyGoogleGeminiModelDefault(cfg); + expect(applied.changed).toBe(false); + expect(applied.next).toEqual(cfg); + }); +}); diff --git a/src/commands/google-gemini-model-default.ts b/src/commands/google-gemini-model-default.ts new file mode 100644 index 000000000..6ae4917db --- /dev/null +++ b/src/commands/google-gemini-model-default.ts @@ -0,0 +1,38 @@ +import type { ClawdbotConfig } from "../config/config.js"; +import type { AgentModelListConfig } from "../config/types.js"; + +export const GOOGLE_GEMINI_DEFAULT_MODEL = "google/gemini-3-pro-preview"; + +function resolvePrimaryModel( + model?: AgentModelListConfig | string, +): string | undefined { + if (typeof model === "string") return model; + if (model && typeof model === "object" && typeof model.primary === "string") { + return model.primary; + } + return undefined; +} + +export function applyGoogleGeminiModelDefault(cfg: ClawdbotConfig): { + next: ClawdbotConfig; + changed: boolean; +} { + const current = resolvePrimaryModel(cfg.agent?.model)?.trim(); + if (current === GOOGLE_GEMINI_DEFAULT_MODEL) { + return { next: cfg, changed: false }; + } + + return { + next: { + ...cfg, + agent: { + ...cfg.agent, + model: + cfg.agent?.model && typeof cfg.agent.model === "object" + ? { ...cfg.agent.model, primary: GOOGLE_GEMINI_DEFAULT_MODEL } + : { primary: GOOGLE_GEMINI_DEFAULT_MODEL }, + }, + }, + changed: true, + }; +} diff --git a/src/commands/onboard-non-interactive.ts b/src/commands/onboard-non-interactive.ts index 8fbc12e72..51bb6ee84 100644 --- a/src/commands/onboard-non-interactive.ts +++ b/src/commands/onboard-non-interactive.ts @@ -23,6 +23,7 @@ import { DEFAULT_GATEWAY_DAEMON_RUNTIME, isGatewayDaemonRuntime, } from "./daemon-runtime.js"; +import { applyGoogleGeminiModelDefault } from "./google-gemini-model-default.js"; import { healthCommand } from "./health.js"; import { applyAuthProfileConfig, @@ -133,6 +134,7 @@ export async function runNonInteractiveOnboarding( provider: "google", mode: "api_key", }); + nextConfig = applyGoogleGeminiModelDefault(nextConfig).next; } else if (authChoice === "claude-cli") { const store = ensureAuthProfileStore(undefined, { allowKeychainPrompt: false,