From a558467c68de586e4840c43e1c620bbafab11391 Mon Sep 17 00:00:00 2001 From: tigerjj Date: Sat, 7 Mar 2026 21:22:20 +0900 Subject: [PATCH] feat: add Gemini CLI provider support --- lib/agents.sh | 38 +++++++++++++- lib/setup-wizard.sh | 53 +++++++++++++++++-- package-lock.json | 4 +- src/lib/agent.ts | 22 ++++++++ src/lib/config.ts | 15 +++++- src/lib/invoke.ts | 61 +++++++++++++++++++++- src/lib/types.ts | 19 ++++++- src/queue-processor.ts | 8 ++- tinyclaw.sh | 82 ++++++++++++++++++++++++++++-- tinyoffice/src/app/agents/page.tsx | 2 + tinyoffice/src/lib/api.ts | 1 + 11 files changed, 288 insertions(+), 17 deletions(-) diff --git a/lib/agents.sh b/lib/agents.sh index 0aae40d1..6eaaaf9c 100644 --- a/lib/agents.sh +++ b/lib/agents.sh @@ -150,10 +150,12 @@ agent_add() { echo " 1) Anthropic (Claude)" echo " 2) OpenAI (Codex)" echo " 3) OpenCode" - read -rp "Choose [1-3, default: 1]: " AGENT_PROVIDER_CHOICE + echo " 4) Gemini CLI" + read -rp "Choose [1-4, default: 1]: " AGENT_PROVIDER_CHOICE case "$AGENT_PROVIDER_CHOICE" in 2) AGENT_PROVIDER="openai" ;; 3) AGENT_PROVIDER="opencode" ;; + 4) AGENT_PROVIDER="gemini" ;; *) AGENT_PROVIDER="anthropic" ;; esac @@ -191,6 +193,21 @@ agent_add() { 8) read -rp "Enter model name (e.g. provider/model): " AGENT_MODEL ;; *) AGENT_MODEL="opencode/claude-sonnet-4-5" ;; esac + elif [ "$AGENT_PROVIDER" = "gemini" ]; then + echo "Model:" + echo " 1) auto (recommended)" + echo " 2) pro (gemini-2.5-pro)" + echo " 3) flash (gemini-2.5-flash)" + echo " 4) flash-lite (gemini-2.5-flash-lite)" + echo " 5) Custom (enter model name)" + read -rp "Choose [1-5, default: 1]: " AGENT_MODEL_CHOICE + case "$AGENT_MODEL_CHOICE" in + 2) AGENT_MODEL="pro" ;; + 3) AGENT_MODEL="flash" ;; + 4) AGENT_MODEL="flash-lite" ;; + 5) read -rp "Enter model name: " AGENT_MODEL ;; + *) AGENT_MODEL="auto" ;; + esac else echo "Model:" echo " 1) GPT-5.3 Codex" @@ -517,15 +534,32 @@ agent_provider() { echo "Use 'tinyclaw agent provider ${agent_id} openai --model {gpt-5.3-codex|gpt-5.2}' to also set the model." fi ;; + gemini) + if [ -n "$model_arg" ]; then + jq --arg id "$agent_id" --arg model "$model_arg" \ + '.agents[$id].provider = "gemini" | .agents[$id].model = $model' \ + "$SETTINGS_FILE" > "$tmp_file" && mv "$tmp_file" "$SETTINGS_FILE" + echo -e "${GREEN}✓ Agent '${agent_id}' switched to Gemini with model: ${model_arg}${NC}" + else + jq --arg id "$agent_id" \ + '.agents[$id].provider = "gemini"' \ + "$SETTINGS_FILE" > "$tmp_file" && mv "$tmp_file" "$SETTINGS_FILE" + echo -e "${GREEN}✓ Agent '${agent_id}' switched to Gemini${NC}" + echo "" + echo "Use 'tinyclaw agent provider ${agent_id} gemini --model {auto|pro|flash|flash-lite}' to also set the model." + fi + ;; *) - echo "Usage: tinyclaw agent provider {anthropic|openai} [--model MODEL_NAME]" + echo "Usage: tinyclaw agent provider {anthropic|openai|gemini} [--model MODEL_NAME]" echo "" echo "Examples:" echo " tinyclaw agent provider coder # Show current provider/model" echo " tinyclaw agent provider coder anthropic # Switch to Anthropic" echo " tinyclaw agent provider coder openai # Switch to OpenAI" + echo " tinyclaw agent provider coder gemini # Switch to Gemini" echo " tinyclaw agent provider coder anthropic --model opus # Switch to Anthropic Opus" echo " tinyclaw agent provider coder openai --model gpt-5.3-codex # Switch to OpenAI GPT-5.3 Codex" + echo " tinyclaw agent provider coder gemini --model flash # Switch to Gemini Flash" exit 1 ;; esac diff --git a/lib/setup-wizard.sh b/lib/setup-wizard.sh index c2ebe29e..e7f3d0d9 100755 --- a/lib/setup-wizard.sh +++ b/lib/setup-wizard.sh @@ -102,13 +102,15 @@ echo "" echo " 1) Anthropic (Claude) (recommended)" echo " 2) OpenAI (Codex/GPT)" echo " 3) OpenCode" +echo " 4) Gemini CLI" echo "" -read -rp "Choose [1-3]: " PROVIDER_CHOICE +read -rp "Choose [1-4]: " PROVIDER_CHOICE case "$PROVIDER_CHOICE" in 1) PROVIDER="anthropic" ;; 2) PROVIDER="openai" ;; 3) PROVIDER="opencode" ;; + 4) PROVIDER="gemini" ;; *) echo -e "${RED}Invalid choice${NC}" exit 1 @@ -176,6 +178,36 @@ elif [ "$PROVIDER" = "opencode" ]; then esac echo -e "${GREEN}✓ Model: $MODEL${NC}" echo "" +elif [ "$PROVIDER" = "gemini" ]; then + echo "Which Gemini model?" + echo "" + echo " 1) auto (recommended)" + echo " 2) pro (gemini-2.5-pro)" + echo " 3) flash (gemini-2.5-flash)" + echo " 4) flash-lite (gemini-2.5-flash-lite)" + echo " 5) Custom (enter model name)" + echo "" + read -rp "Choose [1-5]: " MODEL_CHOICE + + case "$MODEL_CHOICE" in + 1) MODEL="auto" ;; + 2) MODEL="pro" ;; + 3) MODEL="flash" ;; + 4) MODEL="flash-lite" ;; + 5) + read -rp "Enter model name: " MODEL + if [ -z "$MODEL" ]; then + echo -e "${RED}Model name required${NC}" + exit 1 + fi + ;; + *) + echo -e "${RED}Invalid choice${NC}" + exit 1 + ;; + esac + echo -e "${GREEN}✓ Model: $MODEL${NC}" + echo "" else # OpenAI models echo "Which OpenAI model?" @@ -207,7 +239,7 @@ fi # Heartbeat interval echo "Heartbeat interval (seconds)?" -echo -e "${YELLOW}(How often Claude checks in proactively)${NC}" +echo -e "${YELLOW}(How often your default agent checks in proactively)${NC}" echo "" read -rp "Interval in seconds [default: 3600]: " HEARTBEAT_INPUT HEARTBEAT_INTERVAL=${HEARTBEAT_INPUT:-3600} @@ -288,11 +320,12 @@ if [[ "$SETUP_AGENTS" =~ ^[yY] ]]; then read -rp " Display name: " NEW_AGENT_NAME [ -z "$NEW_AGENT_NAME" ] && NEW_AGENT_NAME="$NEW_AGENT_ID" - echo " Provider: 1) Anthropic 2) OpenAI 3) OpenCode" - read -rp " Choose [1-3, default: 1]: " NEW_PROVIDER_CHOICE + echo " Provider: 1) Anthropic 2) OpenAI 3) OpenCode 4) Gemini" + read -rp " Choose [1-4, default: 1]: " NEW_PROVIDER_CHOICE case "$NEW_PROVIDER_CHOICE" in 2) NEW_PROVIDER="openai" ;; 3) NEW_PROVIDER="opencode" ;; + 4) NEW_PROVIDER="gemini" ;; *) NEW_PROVIDER="anthropic" ;; esac @@ -314,6 +347,16 @@ if [[ "$SETUP_AGENTS" =~ ^[yY] ]]; then 5) read -rp " Enter model name (e.g. provider/model): " NEW_MODEL ;; *) NEW_MODEL="opencode/claude-sonnet-4-5" ;; esac + elif [ "$NEW_PROVIDER" = "gemini" ]; then + echo " Model: 1) auto 2) pro 3) flash 4) flash-lite 5) Custom" + read -rp " Choose [1-5, default: 1]: " NEW_MODEL_CHOICE + case "$NEW_MODEL_CHOICE" in + 2) NEW_MODEL="pro" ;; + 3) NEW_MODEL="flash" ;; + 4) NEW_MODEL="flash-lite" ;; + 5) read -rp " Enter model name: " NEW_MODEL ;; + *) NEW_MODEL="auto" ;; + esac else echo " Model: 1) GPT-5.3 Codex 2) GPT-5.2 3) Custom" read -rp " Choose [1-3, default: 1]: " NEW_MODEL_CHOICE @@ -357,6 +400,8 @@ if [ "$PROVIDER" = "anthropic" ]; then MODELS_SECTION='"models": { "provider": "anthropic", "anthropic": { "model": "'"${MODEL}"'" } }' elif [ "$PROVIDER" = "opencode" ]; then MODELS_SECTION='"models": { "provider": "opencode", "opencode": { "model": "'"${MODEL}"'" } }' +elif [ "$PROVIDER" = "gemini" ]; then + MODELS_SECTION='"models": { "provider": "gemini", "gemini": { "model": "'"${MODEL}"'" } }' else MODELS_SECTION='"models": { "provider": "openai", "openai": { "model": "'"${MODEL}"'" } }' fi diff --git a/package-lock.json b/package-lock.json index 033badb1..a8d93c39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tinyclaw", - "version": "0.0.6", + "version": "0.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tinyclaw", - "version": "0.0.6", + "version": "0.0.9", "dependencies": { "@hono/node-server": "^1.19.9", "@types/better-sqlite3": "^7.6.13", diff --git a/src/lib/agent.ts b/src/lib/agent.ts index 794514e2..a75625e1 100644 --- a/src/lib/agent.ts +++ b/src/lib/agent.ts @@ -60,6 +60,11 @@ export function ensureAgentDirectory(agentDir: string): void { fs.copyFileSync(sourceAgents, path.join(agentDir, '.claude', 'CLAUDE.md')); } + // Copy AGENTS.md as GEMINI.md for Gemini CLI context loading. + if (fs.existsSync(sourceAgents)) { + fs.copyFileSync(sourceAgents, path.join(agentDir, 'GEMINI.md')); + } + // Copy default skills from SCRIPT_DIR into .agents/skills const sourceSkills = path.join(SCRIPT_DIR, '.agents', 'skills'); if (fs.existsSync(sourceSkills)) { @@ -144,4 +149,21 @@ export function updateAgentTeammates(agentDir: string, agentId: string, agents: claudeContent = claudeContent.trimEnd() + '\n\n' + startMarker + block + endMarker + '\n'; } fs.writeFileSync(claudeMdPath, claudeContent); + + // Also write to GEMINI.md for Gemini CLI. + const geminiMdPath = path.join(agentDir, 'GEMINI.md'); + let geminiContent = ''; + if (fs.existsSync(geminiMdPath)) { + geminiContent = fs.readFileSync(geminiMdPath, 'utf8'); + } else if (fs.existsSync(agentsMdPath)) { + geminiContent = fs.readFileSync(agentsMdPath, 'utf8'); + } + const gStartIdx = geminiContent.indexOf(startMarker); + const gEndIdx = geminiContent.indexOf(endMarker); + if (gStartIdx !== -1 && gEndIdx !== -1) { + geminiContent = geminiContent.substring(0, gStartIdx + startMarker.length) + block + geminiContent.substring(gEndIdx); + } else { + geminiContent = geminiContent.trimEnd() + '\n\n' + startMarker + block + endMarker + '\n'; + } + fs.writeFileSync(geminiMdPath, geminiContent); } diff --git a/src/lib/config.ts b/src/lib/config.ts index c80b33fa..31847598 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; import { jsonrepair } from 'jsonrepair'; -import { Settings, AgentConfig, TeamConfig, CLAUDE_MODEL_IDS, CODEX_MODEL_IDS, OPENCODE_MODEL_IDS } from './types'; +import { Settings, AgentConfig, TeamConfig, CLAUDE_MODEL_IDS, CODEX_MODEL_IDS, GEMINI_MODEL_IDS, OPENCODE_MODEL_IDS } from './types'; export const SCRIPT_DIR = path.resolve(__dirname, '../..'); const _localTinyclaw = path.join(SCRIPT_DIR, '.tinyclaw'); @@ -45,6 +45,9 @@ export function getSettings(): Settings { if (settings?.models?.openai) { if (!settings.models) settings.models = {}; settings.models.provider = 'openai'; + } else if (settings?.models?.gemini) { + if (!settings.models) settings.models = {}; + settings.models.provider = 'gemini'; } else if (settings?.models?.opencode) { if (!settings.models) settings.models = {}; settings.models.provider = 'opencode'; @@ -69,6 +72,8 @@ export function getDefaultAgentFromModels(settings: Settings): AgentConfig { let model = ''; if (provider === 'openai') { model = settings?.models?.openai?.model || 'gpt-5.3-codex'; + } else if (provider === 'gemini') { + model = settings?.models?.gemini?.model || 'auto'; } else if (provider === 'opencode') { model = settings?.models?.opencode?.model || 'sonnet'; } else { @@ -120,6 +125,14 @@ export function resolveCodexModel(model: string): string { return CODEX_MODEL_IDS[model] || model || ''; } +/** + * Resolve the model ID for Gemini CLI (passed via --model flag). + * Falls back to the raw model string from settings if no mapping is found. + */ +export function resolveGeminiModel(model: string): string { + return GEMINI_MODEL_IDS[model] || model || ''; +} + /** * Resolve the model ID for OpenCode (passed via --model flag). * Falls back to the raw model string from settings if no mapping is found. diff --git a/src/lib/invoke.ts b/src/lib/invoke.ts index c763939f..1433d3d4 100644 --- a/src/lib/invoke.ts +++ b/src/lib/invoke.ts @@ -2,7 +2,7 @@ import { spawn } from 'child_process'; import fs from 'fs'; import path from 'path'; import { AgentConfig, TeamConfig } from './types'; -import { SCRIPT_DIR, resolveClaudeModel, resolveCodexModel, resolveOpenCodeModel } from './config'; +import { SCRIPT_DIR, resolveClaudeModel, resolveCodexModel, resolveGeminiModel, resolveOpenCodeModel } from './config'; import { log } from './logging'; import { ensureAgentDirectory, updateAgentTeammates } from './agent'; @@ -48,7 +48,7 @@ export async function runCommand(command: string, args: string[], cwd?: string): } /** - * Invoke a single agent with a message. Contains all Claude/Codex invocation logic. + * Invoke a single agent with a message. Contains provider-specific invocation logic. * Returns the raw response text. */ export async function invokeAgent( @@ -156,6 +156,63 @@ export async function invokeAgent( } return response || 'Sorry, I could not generate a response from OpenCode.'; + } else if (provider === 'gemini') { + // Gemini CLI — non-interactive mode via --prompt. + // Uses --output-format json to return a single JSON object with a "response" field. + // Uses --resume latest for session continuation and retries fresh if resume is unavailable. + const modelId = resolveGeminiModel(agent.model); + log('INFO', `Using Gemini CLI (agent: ${agentId}, model: ${modelId || 'auto'})`); + + const continueConversation = !shouldReset; + + if (shouldReset) { + log('INFO', `🔄 Resetting Gemini conversation for agent: ${agentId}`); + } + + const buildGeminiArgs = (withResume: boolean) => { + const args = ['--output-format', 'json', '--approval-mode', 'yolo']; + if (modelId) { + args.push('--model', modelId); + } + if (withResume) { + args.push('--resume', 'latest'); + } + args.push('--prompt', message); + return args; + }; + + const parseGeminiOutput = (output: string): string | null => { + const trimmed = output.trim(); + if (!trimmed) return null; + try { + const json = JSON.parse(trimmed); + if (typeof json.response === 'string' && json.response.trim()) { + return json.response; + } + if (json?.error?.message) { + return `Gemini CLI error: ${json.error.message}`; + } + } catch { + return trimmed; + } + return null; + }; + + let geminiOutput: string; + try { + geminiOutput = await runCommand('gemini', buildGeminiArgs(continueConversation), workingDir); + } catch (err: any) { + const errMsg = String(err?.message || ''); + const resumeUnavailable = /(error resuming session|no .*session|session not found)/i.test(errMsg); + if (continueConversation && resumeUnavailable) { + log('INFO', `No resumable Gemini session for agent ${agentId}, starting fresh`); + geminiOutput = await runCommand('gemini', buildGeminiArgs(false), workingDir); + } else { + throw err; + } + } + + return parseGeminiOutput(geminiOutput) || 'Sorry, I could not generate a response from Gemini.'; } else { // Default to Claude (Anthropic) log('INFO', `Using Claude provider (agent: ${agentId})`); diff --git a/src/lib/types.ts b/src/lib/types.ts index 65bd1f10..bc167449 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,6 +1,6 @@ export interface AgentConfig { name: string; - provider: string; // 'anthropic', 'openai', or 'opencode' + provider: string; // 'anthropic', 'openai', 'opencode', or 'gemini' model: string; // e.g. 'sonnet', 'opus', 'gpt-5.3-codex' working_directory: string; system_prompt?: string; @@ -43,7 +43,7 @@ export interface Settings { whatsapp?: {}; }; models?: { - provider?: string; // 'anthropic', 'openai', or 'opencode' + provider?: string; // 'anthropic', 'openai', 'opencode', or 'gemini' anthropic?: { model?: string; }; @@ -53,6 +53,9 @@ export interface Settings { opencode?: { model?: string; }; + gemini?: { + model?: string; + }; }; agents?: Record; teams?: Record; @@ -119,6 +122,18 @@ export const CODEX_MODEL_IDS: Record = { 'gpt-5.3-codex': 'gpt-5.3-codex', }; +// Gemini CLI model IDs. Falls back to the raw model string. +export const GEMINI_MODEL_IDS: Record = { + 'auto': 'auto', + 'pro': 'gemini-2.5-pro', + 'flash': 'gemini-2.5-flash', + 'flash-lite': 'gemini-2.5-flash-lite', + 'gemini-2.5-pro': 'gemini-2.5-pro', + 'gemini-2.5-flash': 'gemini-2.5-flash', + 'gemini-2.5-flash-lite': 'gemini-2.5-flash-lite', + 'gemini-3-pro-preview': 'gemini-3-pro-preview', +}; + // OpenCode model IDs in provider/model format (passed via --model / -m flag). // Falls back to the raw model string from settings if no mapping is found. export const OPENCODE_MODEL_IDS: Record = { diff --git a/src/queue-processor.ts b/src/queue-processor.ts index 32bec7f1..1dccdca7 100644 --- a/src/queue-processor.ts +++ b/src/queue-processor.ts @@ -167,7 +167,13 @@ async function processMessage(dbMsg: DbMessage): Promise { response = await invokeAgent(agent, agentId, message, workspacePath, shouldReset, agents, teams); } catch (error) { const provider = agent.provider || 'anthropic'; - const providerLabel = provider === 'openai' ? 'Codex' : provider === 'opencode' ? 'OpenCode' : 'Claude'; + const providerLabel = provider === 'openai' + ? 'Codex' + : provider === 'opencode' + ? 'OpenCode' + : provider === 'gemini' + ? 'Gemini' + : 'Claude'; log('ERROR', `${providerLabel} error (agent: ${agentId}): ${(error as Error).message}`); response = "Sorry, I encountered an error processing your request. Please check the queue logs."; } diff --git a/tinyclaw.sh b/tinyclaw.sh index 582a3d9f..2db06ef1 100755 --- a/tinyclaw.sh +++ b/tinyclaw.sh @@ -96,6 +96,10 @@ case "${1:-}" in CURRENT_PROVIDER=$(jq -r '.models.provider // "anthropic"' "$SETTINGS_FILE" 2>/dev/null) if [ "$CURRENT_PROVIDER" = "openai" ]; then CURRENT_MODEL=$(jq -r '.models.openai.model // empty' "$SETTINGS_FILE" 2>/dev/null) + elif [ "$CURRENT_PROVIDER" = "gemini" ]; then + CURRENT_MODEL=$(jq -r '.models.gemini.model // empty' "$SETTINGS_FILE" 2>/dev/null) + elif [ "$CURRENT_PROVIDER" = "opencode" ]; then + CURRENT_MODEL=$(jq -r '.models.opencode.model // empty' "$SETTINGS_FILE" 2>/dev/null) else CURRENT_MODEL=$(jq -r '.models.anthropic.model // empty' "$SETTINGS_FILE" 2>/dev/null) fi @@ -197,16 +201,49 @@ case "${1:-}" in echo "Note: Make sure you have the 'codex' CLI installed and authenticated." fi ;; + gemini) + if [ ! -f "$SETTINGS_FILE" ]; then + echo -e "${RED}No settings file found. Run setup first.${NC}" + exit 1 + fi + + # Switch to Gemini provider + tmp_file="$SETTINGS_FILE.tmp" + if [ -n "$MODEL_ARG" ]; then + UPDATED_COUNT=$(jq --arg old_provider "$OLD_PROVIDER" '[.agents // {} | to_entries[] | select(.value.provider == $old_provider)] | length' "$SETTINGS_FILE" 2>/dev/null) + jq --arg model "$MODEL_ARG" --arg old_provider "$OLD_PROVIDER" ' + .models.provider = "gemini" | + .models.gemini.model = $model | + .agents //= {} | + .agents |= with_entries( + if .value.provider == $old_provider then .value.provider = "gemini" | .value.model = $model else . end + ) + ' "$SETTINGS_FILE" > "$tmp_file" && mv "$tmp_file" "$SETTINGS_FILE" + echo -e "${GREEN}✓ Switched to Gemini provider with model: $MODEL_ARG${NC}" + if [ "$UPDATED_COUNT" -gt 0 ] 2>/dev/null; then + echo -e "${BLUE} Updated $UPDATED_COUNT agent(s) from $OLD_PROVIDER to gemini/$MODEL_ARG${NC}" + fi + echo "" + echo "Note: Make sure you have the 'gemini' CLI installed and authenticated." + else + jq '.models.provider = "gemini"' "$SETTINGS_FILE" > "$tmp_file" && mv "$tmp_file" "$SETTINGS_FILE" + echo -e "${GREEN}✓ Switched to Gemini provider${NC}" + echo "" + echo "Use 'tinyclaw model {auto|pro|flash|flash-lite}' to set the model." + echo "Note: Make sure you have the 'gemini' CLI installed and authenticated." + fi + ;; *) - echo "Usage: $0 provider {anthropic|openai} [--model MODEL_NAME]" + echo "Usage: $0 provider {anthropic|openai|gemini} [--model MODEL_NAME]" echo "" echo "Examples:" echo " $0 provider # Show current provider and model" echo " $0 provider anthropic # Switch to Anthropic" echo " $0 provider openai # Switch to OpenAI" + echo " $0 provider gemini # Switch to Gemini" echo " $0 provider anthropic --model sonnet # Switch to Anthropic with Sonnet" echo " $0 provider openai --model gpt-5.3-codex # Switch to OpenAI with GPT-5.3 Codex" - echo " $0 provider openai --model gpt-4o # Switch to OpenAI with custom model" + echo " $0 provider gemini --model auto # Switch to Gemini with Auto" exit 1 ;; esac @@ -218,6 +255,10 @@ case "${1:-}" in CURRENT_PROVIDER=$(jq -r '.models.provider // "anthropic"' "$SETTINGS_FILE" 2>/dev/null) if [ "$CURRENT_PROVIDER" = "openai" ]; then CURRENT_MODEL=$(jq -r '.models.openai.model // empty' "$SETTINGS_FILE" 2>/dev/null) + elif [ "$CURRENT_PROVIDER" = "gemini" ]; then + CURRENT_MODEL=$(jq -r '.models.gemini.model // empty' "$SETTINGS_FILE" 2>/dev/null) + elif [ "$CURRENT_PROVIDER" = "opencode" ]; then + CURRENT_MODEL=$(jq -r '.models.opencode.model // empty' "$SETTINGS_FILE" 2>/dev/null) else CURRENT_MODEL=$(jq -r '.models.anthropic.model // empty' "$SETTINGS_FILE" 2>/dev/null) fi @@ -291,8 +332,32 @@ case "${1:-}" in echo "" echo "Note: Changes take effect on next message." ;; + auto|pro|flash|flash-lite|gemini-2.5-pro|gemini-2.5-flash|gemini-2.5-flash-lite|gemini-3-pro-preview) + if [ ! -f "$SETTINGS_FILE" ]; then + echo -e "${RED}No settings file found. Run setup first.${NC}" + exit 1 + fi + + # Update global default and propagate to all gemini agents + tmp_file="$SETTINGS_FILE.tmp" + jq --arg model "$2" ' + .models.gemini.model = $model | + .agents //= {} | + .agents |= with_entries( + if .value.provider == "gemini" then .value.model = $model else . end + ) + ' "$SETTINGS_FILE" > "$tmp_file" && mv "$tmp_file" "$SETTINGS_FILE" + + UPDATED_COUNT=$(jq --arg model "$2" '[.agents // {} | to_entries[] | select(.value.provider == "gemini")] | length' "$SETTINGS_FILE" 2>/dev/null) + echo -e "${GREEN}✓ Model switched to: $2${NC}" + if [ "$UPDATED_COUNT" -gt 0 ] 2>/dev/null; then + echo -e "${BLUE} Updated $UPDATED_COUNT gemini agent(s)${NC}" + fi + echo "" + echo "Note: Changes take effect on next message." + ;; *) - echo "Usage: $0 model {sonnet|opus|gpt-5.2|gpt-5.3-codex}" + echo "Usage: $0 model {sonnet|opus|gpt-5.2|gpt-5.3-codex|auto|pro|flash|flash-lite}" echo "" echo "Anthropic models:" echo " sonnet # Claude Sonnet (fast)" @@ -302,10 +367,17 @@ case "${1:-}" in echo " gpt-5.3-codex # GPT-5.3 Codex" echo " gpt-5.2 # GPT-5.2" echo "" + echo "Gemini models:" + echo " auto # Let Gemini choose" + echo " pro # Gemini Pro" + echo " flash # Gemini Flash" + echo " flash-lite # Gemini Flash Lite" + echo "" echo "Examples:" echo " $0 model # Show current model" echo " $0 model sonnet # Switch to Claude Sonnet" echo " $0 model gpt-5.3-codex # Switch to GPT-5.3 Codex" + echo " $0 model flash # Switch to Gemini Flash" exit 1 ;; esac @@ -349,8 +421,10 @@ case "${1:-}" in echo " $0 agent provider coder # Show current provider/model" echo " $0 agent provider coder anthropic # Switch to Anthropic" echo " $0 agent provider coder openai # Switch to OpenAI" + echo " $0 agent provider coder gemini # Switch to Gemini" echo " $0 agent provider coder anthropic --model opus # Switch to Anthropic Opus" echo " $0 agent provider coder openai --model gpt-5.3-codex # Switch to OpenAI GPT-5.3 Codex" + echo " $0 agent provider coder gemini --model flash # Switch to Gemini Flash" exit 1 fi agent_provider "$3" "$4" "$5" "$6" @@ -374,6 +448,7 @@ case "${1:-}" in echo " $0 agent reset coder" echo " $0 agent reset coder researcher" echo " $0 agent provider coder anthropic --model opus" + echo " $0 agent provider coder gemini --model flash" echo "" echo "In chat, use '@agent_id message' to route to a specific agent." exit 1 @@ -500,6 +575,7 @@ case "${1:-}" in echo " $0 start" echo " $0 status" echo " $0 provider openai --model gpt-5.3-codex" + echo " $0 provider gemini --model flash" echo " $0 model opus" echo " $0 reset coder" echo " $0 reset coder researcher" diff --git a/tinyoffice/src/app/agents/page.tsx b/tinyoffice/src/app/agents/page.tsx index 581111b2..988c003b 100644 --- a/tinyoffice/src/app/agents/page.tsx +++ b/tinyoffice/src/app/agents/page.tsx @@ -212,6 +212,7 @@ function AgentEditor({ +
@@ -287,6 +288,7 @@ function AgentCard({ anthropic: "bg-orange-500/10 text-orange-600 dark:text-orange-400", openai: "bg-green-500/10 text-green-600 dark:text-green-400", opencode: "bg-blue-500/10 text-blue-600 dark:text-blue-400", + gemini: "bg-cyan-500/10 text-cyan-600 dark:text-cyan-400", }; return ( diff --git a/tinyoffice/src/lib/api.ts b/tinyoffice/src/lib/api.ts index 14fbcfbd..d9cc0af8 100644 --- a/tinyoffice/src/lib/api.ts +++ b/tinyoffice/src/lib/api.ts @@ -42,6 +42,7 @@ export interface Settings { anthropic?: { model?: string }; openai?: { model?: string }; opencode?: { model?: string }; + gemini?: { model?: string }; }; agents?: Record; teams?: Record;