Conversation
Greptile SummaryThis PR integrates Gemini CLI as a fourth AI provider alongside Anthropic, OpenAI, and OpenCode, following the same provider-plugin pattern established in PR #159 (Cursor CLI). The change is broad but mostly additive: new branches in Key changes:
Issues found:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant QP as queue-processor.ts
participant IA as invokeAgent()
participant GC as gemini CLI
QP->>IA: invokeAgent(agent, agentId, message, ...)
IA->>IA: ensureAgentDirectory() — copies AGENTS.md → GEMINI.md
IA->>IA: updateAgentTeammates() — syncs GEMINI.md teammate block
IA->>IA: resolveGeminiModel(agent.model)
alt continueConversation (shouldReset=false)
IA->>GC: gemini --output-format json --approval-mode yolo [--model id] --resume latest --prompt msg
alt Success
GC-->>IA: JSON { response: "..." }
IA->>IA: parseGeminiOutput → return response
else Error matching resume regex
GC-->>IA: Error: "no session / session not found / ..."
IA->>GC: gemini --output-format json --approval-mode yolo [--model id] --prompt msg
GC-->>IA: JSON { response: "..." }
IA->>IA: parseGeminiOutput → return response
end
else shouldReset=true
IA->>GC: gemini --output-format json --approval-mode yolo [--model id] --prompt msg
GC-->>IA: JSON { response: "..." }
IA->>IA: parseGeminiOutput → return response
end
IA-->>QP: response string
Last reviewed commit: a558467 |
|
|
||
| // Gemini CLI model IDs. Falls back to the raw model string. | ||
| export const GEMINI_MODEL_IDS: Record<string, string> = { | ||
| 'auto': 'auto', |
There was a problem hiding this comment.
'auto' model resolves to a truthy string, so --model auto is always passed
GEMINI_MODEL_IDS['auto'] is set to the non-empty string 'auto'. In invoke.ts, resolveGeminiModel('auto') therefore returns 'auto', which is truthy — so the guard if (modelId) passes and --model auto is always appended to the Gemini CLI args.
The intent of the auto option, as described in the setup wizard ("Let Gemini choose"), is to let the CLI pick a model automatically, which typically means not passing --model at all. If the Gemini CLI does not accept --model auto as a valid argument, every invocation using the default model setting will fail.
The fix is to map 'auto' to an empty string so the flag is omitted:
| 'auto': 'auto', | |
| 'auto': '', |
Alternatively, add an explicit guard in invoke.ts:
if (modelId && modelId !== 'auto') {
args.push('--model', modelId);
}| 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); |
There was a problem hiding this comment.
Resume-error regex may be too narrow
The pattern /(error resuming session|no .*session|session not found)/i is hardcoded against three specific Gemini CLI error phrases. If the actual CLI emits a differently-worded message (e.g., "Session does not exist", "Cannot resume session", or any localised variant), the retry-without-resume fallback will never trigger — the original error will propagate instead and the agent will appear broken on first use.
Consider expanding the regex to cover more variants or, better, also catching the exit code if the Gemini CLI uses a distinctive non-zero code for "no session" errors:
| const resumeUnavailable = /(error resuming session|no .*session|session not found)/i.test(errMsg); | |
| const resumeUnavailable = /(error resuming|no .*session|session not found|cannot resume|session does not exist)/i.test(errMsg); |
| 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:" |
There was a problem hiding this comment.
Gemini model names accepted even when the active provider is not Gemini
The model sub-command now has a dedicated case for Gemini model aliases (auto|pro|flash|flash-lite|…). This case always updates .models.gemini.model and all Gemini agents, regardless of which provider is currently active.
If a user on Anthropic or OpenAI accidentally types tinyclaw model auto, they will see the success message "✓ Model switched to: auto" even though their active provider's model was not changed. This is misleading and could cause confusion.
Consider guarding this branch with a check of the current provider — or at minimum printing a note when the update is applied to Gemini settings while a different provider is active:
CURRENT_PROVIDER=$(jq -r '.models.provider // "anthropic"' "$SETTINGS_FILE" 2>/dev/null)
if [ "$CURRENT_PROVIDER" != "gemini" ]; then
echo -e "${YELLOW}Note: Active provider is '$CURRENT_PROVIDER'. Gemini model preference saved but not yet active.${NC}"
fi
Summary
Adds Gemini CLI (
gemini) as an additional AI provider alongside Anthropic (Claude), OpenAI (Codex), and OpenCode.This follows the same provider integration pattern as PR #159 (Cursor CLI support), adapted for Gemini CLI invocation and settings flow.
What changed
Runtime integration (
src/lib)src/lib/types.tsgeminiprovider support in settings/typesGEMINI_MODEL_IDSmapping (auto,pro,flash,flash-lite, explicit model IDs)src/lib/config.tsmodels.geminiauto)resolveGeminiModel()src/lib/invoke.tsgemini --output-format json --approval-mode yolo [--model ...] [--resume latest] --prompt ...responsefield), with text fallback--resumewhen no previous session is availablesrc/queue-processor.tsAgent workspace context sync
src/lib/agent.tsAGENTS.mdtoGEMINI.mdfor new agent workspacesGEMINI.mdsynchronized (same asAGENTS.md/.claude/CLAUDE.md)CLI / setup script support
tinyclaw.shprovider gemini [--model ...]tinyclaw model ...lib/agents.shagent addprovider selectionagent provider ...lib/setup-wizard.sh.models.gemini.modelwhen selectedTinyOffice UI/types
tinyoffice/src/lib/api.tsmodels.geminitypetinyoffice/src/app/agents/page.tsxgeminiprovider option in editorValidation
npm run build(root)bash -n tinyclaw.sh lib/agents.sh lib/setup-wizard.shnpm --prefix tinyoffice run buildtinyclaw provider gemini --model flashtinyclaw model auto(propagates to gemini agents)tinyclaw agent provider <id> gemini --model proNotes
package-lock.jsonwas updated to remain in sync with current package version metadata.