Skip to content

feat: add Gemini CLI provider support#165

Closed
tigerjj wants to merge 1 commit intoTinyAGI:mainfrom
tigerjj:feat/support-gemini-cli
Closed

feat: add Gemini CLI provider support#165
tigerjj wants to merge 1 commit intoTinyAGI:mainfrom
tigerjj:feat/support-gemini-cli

Conversation

@tigerjj
Copy link

@tigerjj tigerjj commented Mar 7, 2026

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.ts
    • Added gemini provider support in settings/types
    • Added GEMINI_MODEL_IDS mapping (auto, pro, flash, flash-lite, explicit model IDs)
  • src/lib/config.ts
    • Added provider auto-detection for models.gemini
    • Added gemini default model fallback (auto)
    • Added resolveGeminiModel()
  • src/lib/invoke.ts
    • Added Gemini provider branch:
      • Command: gemini --output-format json --approval-mode yolo [--model ...] [--resume latest] --prompt ...
      • Parses JSON output (response field), with text fallback
      • Retries without --resume when no previous session is available
  • src/queue-processor.ts
    • Added provider error label for Gemini

Agent workspace context sync

  • src/lib/agent.ts
    • Copies AGENTS.md to GEMINI.md for new agent workspaces
    • Keeps teammate block in GEMINI.md synchronized (same as AGENTS.md / .claude/CLAUDE.md)

CLI / setup script support

  • tinyclaw.sh
    • Added provider gemini [--model ...]
    • Added gemini model support to tinyclaw model ...
    • Provider/model display now reads gemini/opencode model paths correctly
  • lib/agents.sh
    • Added Gemini in agent add provider selection
    • Added Gemini handling in agent provider ...
  • lib/setup-wizard.sh
    • Added Gemini provider/model selection in initial setup and additional-agent flow
    • Writes .models.gemini.model when selected

TinyOffice UI/types

  • tinyoffice/src/lib/api.ts
    • Added models.gemini type
  • tinyoffice/src/app/agents/page.tsx
    • Added gemini provider option in editor
    • Added Gemini provider badge color

Validation

  • npm run build (root)
  • bash -n tinyclaw.sh lib/agents.sh lib/setup-wizard.sh
  • npm --prefix tinyoffice run build
  • ✅ CLI behavior checked with temporary settings:
    • tinyclaw provider gemini --model flash
    • tinyclaw model auto (propagates to gemini agents)
    • tinyclaw agent provider <id> gemini --model pro
  • ✅ Gemini invoke path validated with stub CLI:
    • resume attempt + no-session fallback
    • JSON response parsing

Notes

  • package-lock.json was updated to remain in sync with current package version metadata.

@greptile-apps
Copy link

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This 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 invoke.ts, config.ts, types.ts, shell scripts, and the TinyOffice UI all mirror the structure of existing providers cleanly.

Key changes:

  • src/lib/types.ts — Adds GEMINI_MODEL_IDS mapping (auto, pro, flash, flash-lite, explicit IDs) and gemini to the Settings type.
  • src/lib/config.ts — Adds Gemini auto-detection, default model fallback, and resolveGeminiModel().
  • src/lib/invoke.ts — Adds the Gemini invocation branch: JSON output parsing, --resume latest continuation with a no-session retry fallback, and --approval-mode yolo for unattended execution.
  • src/lib/agent.ts — Copies AGENTS.md as GEMINI.md for all new agent directories and keeps the teammate block synchronized on each invocation (mirrors the .claude/CLAUDE.md pattern).
  • Shell scripts (tinyclaw.sh, lib/agents.sh, lib/setup-wizard.sh) — Add Gemini provider/model selection throughout the CLI and setup wizard.
  • TinyOffice (page.tsx, api.ts) — Adds Gemini option in the agent editor and a cyan provider badge.

Issues found:

  • Logic (medium): GEMINI_MODEL_IDS['auto'] maps to the non-empty string 'auto', so --model auto is always appended to the CLI args even when the intent is to omit the flag and let the Gemini CLI pick automatically. If the Gemini CLI does not accept --model auto, every default invocation will fail. (src/lib/types.ts:127)
  • Style: The resume-error retry regex /(error resuming session|no .*session|session not found)/i is narrowly hardcoded and may not match the actual Gemini CLI error messages, leaving the no-session fallback unreachable. (src/lib/invoke.ts:206)
  • Style: Gemini-specific model names in tinyclaw model (e.g. tinyclaw model auto) always update .models.gemini.model regardless of the active provider, producing a misleading success message for non-Gemini users. (tinyclaw.sh:332)

Confidence Score: 3/5

  • Functionally risky to merge as-is due to the --model auto issue that could silently break all default Gemini invocations.
  • The overall integration is well-structured and follows established patterns, but the GEMINI_MODEL_IDS['auto'] = 'auto' mapping is a likely runtime defect: since 'auto' is truthy, --model auto will always be passed to the Gemini CLI for the default model setting. If the Gemini CLI doesn't accept auto as a model identifier, every default Gemini invocation will fail. The fragile resume-error regex and the misleading tinyclaw model feedback are lower-severity but still worth addressing before merge.
  • src/lib/types.ts (GEMINI_MODEL_IDS auto mapping) and src/lib/invoke.ts (resume error regex) require attention before merge.

Important Files Changed

Filename Overview
src/lib/invoke.ts Adds Gemini CLI provider branch with JSON output parsing, resume/retry logic, and --approval-mode yolo. The resume-error regex is fragile and may miss actual Gemini CLI error messages, preventing the no-resume fallback from triggering.
src/lib/types.ts Adds GEMINI_MODEL_IDS mapping and Gemini settings type. Critical issue: 'auto' maps to the non-empty string 'auto', causing --model auto to always be passed to the Gemini CLI even when the intent is to omit the flag and let the CLI pick automatically.
src/lib/config.ts Adds Gemini auto-detection, default model fallback (auto), and resolveGeminiModel(). Follows the same pattern as existing providers cleanly.
src/lib/agent.ts Copies AGENTS.md as GEMINI.md on agent directory init and keeps it synchronized in updateAgentTeammates. Logic is sound and follows the same pattern as .claude/CLAUDE.md sync. Note: GEMINI.md is created for all agents regardless of provider.
tinyclaw.sh Adds provider gemini and Gemini model name cases. The Gemini model case (`auto
lib/agents.sh Adds Gemini as a provider choice (option 4) in agent add and agent provider flows. Changes are well-structured and follow existing patterns.
lib/setup-wizard.sh Adds Gemini provider/model selection to both initial setup and additional-agent flows; writes .models.gemini.model correctly. Also improves the heartbeat interval prompt to be provider-agnostic.
src/queue-processor.ts Adds 'Gemini' label to the provider error message ternary. Purely additive and correct.
tinyoffice/src/app/agents/page.tsx Adds gemini provider option to the agent editor dropdown and a cyan badge color for the provider card. Clean, no issues.
tinyoffice/src/lib/api.ts Adds gemini?: { model?: string } to the Settings interface, keeping the frontend type in sync with the backend. No issues.

Sequence Diagram

sequenceDiagram
    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
Loading

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',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'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:

Suggested change
'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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
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);

Comment on lines 332 to 362
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:"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant