This document tracks Gemini as a first-class TermAl agent.
Reference: agent-integration-comparison.md
Implemented in TermAl via the shared ACP adapter.
TermAl currently uses Gemini’s ACP mode rather than the older one-shot
stream-json path. The option comparison below is kept as protocol reference,
but the shipped integration is the bidirectional ACP session model.
Gemini CLI is now wired through session creation, runtime spawning, message dispatch, approval handling, and frontend rendering. The remaining questions are about protocol hardening and UX polish, not whether Gemini belongs in TermAl.
Gemini CLI offers two integration paths. The recommended path depends on how mature ACP support is at the time of implementation.
stream-json headless mode (simpler, unidirectional)Spawn the CLI in non-interactive mode and parse its JSONL stdout. This is the simplest path and works today.
gemini -p "<prompt>" --output-format stream-json \
--approval-mode yolo \
--model auto \
--sandbox
Pros: Simple to implement, stable API, no experimental flags. Cons: Unidirectional (stdout only), no mid-turn approval, no session resume within a single process, must re-spawn per turn.
--experimental-acp mode (richer, bidirectional)Source: https://geminicli.com/docs/cli/cli-reference/
Run Gemini as a full ACP (Agent Client Protocol) server over stdio. This gives the same JSON-RPC 2.0 bidirectional protocol that Cursor uses, making it structurally identical to the Cursor adapter.
gemini --experimental-acp
Pros: Bidirectional JSON-RPC, permission handling, session management, same protocol as Cursor (shared adapter logic possible). Cons: Experimental flag — may change. Requires more upfront protocol work.
TermAl now uses Option B (--experimental-acp) so Gemini can share the ACP
runtime path with Cursor and expose session-scoped model refresh, approval-mode
controls, and live permission handling.
| Property | Value |
|---|---|
| Transport | Spawned child process per turn |
| Output | stdout, newline-delimited JSON (JSONL) |
| Input | Single prompt via -p flag (no stdin interaction) |
| Direction | Unidirectional (server → client only) |
gemini -p "<prompt>" \
--output-format stream-json \
--approval-mode <yolo|auto_edit|default> \
--model <auto|pro|flash|flash-lite> \
-r <session-index-or-"latest"> # optional: resume session
| Flag | Short | Description |
|---|---|---|
--prompt |
-p |
Prompt text; forces non-interactive mode |
--output-format |
-o |
text, json, or stream-json |
--model |
-m |
auto, pro, flash, flash-lite |
--approval-mode |
default, auto_edit, yolo |
|
--sandbox |
-s |
Run in sandboxed environment |
--resume |
-r |
Resume session by index or "latest" |
--extensions |
-e |
Comma-separated list of enabled extensions |
--allowed-mcp-server-names |
Filter allowed MCP servers | |
--include-directories |
Extra workspace directories | |
--debug |
-d |
Verbose logging |
Each line of stdout is a JSON object with a type field:
initEmitted once at the start.
{
"type": "init",
"timestamp": "2025-10-10T12:00:00.000Z",
"session_id": "abc123",
"model": "gemini-2.0-flash-exp"
}
messageUser echo and assistant response chunks.
{
"type": "message",
"role": "assistant",
"content": "I'll investigate the auth middleware...",
"delta": true,
"timestamp": "2025-10-10T12:00:04.000Z"
}
When delta is true, the content is an incremental chunk (map to
TextDeltaEvent). When false or absent, it is a complete message.
tool_useTool invocation request.
{
"type": "tool_use",
"tool_name": "Bash",
"tool_id": "bash-123",
"parameters": { "command": "npm test" },
"timestamp": "2025-10-10T12:00:02.000Z"
}
tool_resultOutput from an executed tool.
{
"type": "tool_result",
"tool_id": "bash-123",
"status": "success",
"output": "Tests passing: 12/12\n",
"timestamp": "2025-10-10T12:00:03.000Z"
}
errorNon-fatal warnings and system errors.
{
"type": "error",
"message": "Rate limit exceeded, retrying...",
"timestamp": "2025-10-10T12:00:06.000Z"
}
resultFinal event — marks end of the turn.
{
"type": "result",
"status": "success",
"stats": {
"total_tokens": 250,
"input_tokens": 50,
"output_tokens": 200,
"duration_ms": 3000,
"tool_calls": 1
},
"timestamp": "2025-10-10T12:00:05.000Z"
}
| Code | Meaning |
|---|---|
0 |
Success |
1 |
General error or API failure |
42 |
Input error (invalid prompt or arguments) |
53 |
Turn limit exceeded |
| Property | Value |
|---|---|
| Storage format | Single .json per session |
| Location | ~/.gemini/tmp/<project>/chats/session-{ts}-{id}.json |
| Resume mechanism | --resume <index> or --resume latest |
| Session discovery | Scan chats/ directory |
| Cross-session memory | GEMINI.md project instructions and ~/.gemini/memory.md |
| Ignore file | .geminiignore |
| Property | Value |
|---|---|
| Transport | stdio (stdin/stdout) |
| Envelope | JSON-RPC 2.0 |
| Framing | Newline-delimited JSON |
| Direction | Bidirectional |
gemini --experimental-acp
Gemini’s ACP mode implements the same Agent Client Protocol used by Cursor CLI. The core methods are identical:
initialize — handshake with protocol version and client capabilitiessession/new — create a new sessionsession/load — resume an existing sessionsession/prompt — send a user promptsession/cancel — cancel the current turnsession/update — streaming notifications (server → client)session/request_permission — tool approval requests (server → client)This means a shared ACP adapter could serve both Cursor and Gemini, with agent-specific differences isolated to:
cursor vs gemini)agent acp vs --experimental-acp)cursor/*, Gemini may differ)# OAuth (interactive browser flow)
gemini --experimental-acp # prompts for login on first use
# API key
GEMINI_API_KEY=... gemini --experimental-acp
# Vertex AI
# Configured via gcloud credentials
| Gemini event | TermAl equivalent | Notes |
|---|---|---|
init |
Session metadata | Extract session_id, model |
message (delta) |
TextDeltaEvent |
Streaming text into chat bubble |
message (complete) |
TextMessage |
Full assistant response |
tool_use |
CommandMessage (status: running) |
Show tool invocation |
tool_result |
CommandMessage (status: success/error) |
Show tool output |
error |
Error card or inline warning | Surface in chat |
result |
Turn completion | End turn, update session status |
Same as the Cursor ACP mapping — see
cursor-cli-integration.md.
Gemini’s non-interactive mode has a known limitation: it cannot prompt for tool approval mid-turn. The workarounds are:
--approval-mode |
Behavior |
|---|---|
yolo |
Auto-approve everything (no approval cards in TermAl) |
auto_edit |
Auto-approve file edits, prompt for dangerous commands |
default |
Prompt for approval — hangs in non-interactive mode |
For stream-json integration, TermAl should default to auto_edit or yolo and
surface the choice in session settings. True interactive approval requires
ACP mode (Option B).
Full bidirectional approval via session/request_permission, identical to the
Cursor flow. TermAl can intercept and surface approval cards.
Add a Gemini variant to each of these enums in src/main.rs:
Agent — Gemini (with name() → "Gemini", avatar() → "GM")SessionRuntime — Gemini(GeminiRuntimeHandle)RuntimeToken — Gemini(String)KillableRuntime — Gemini(GeminiRuntimeHandle)TurnDispatch — PersistentGemini { command, sender, session_id }GeminiRuntimeHandle — runtime_id, input_tx, processGeminiRuntimeCommand — Prompt(GeminiPromptCommand) |
(ACP: ApprovalResponse(…)) |
GeminiPromptCommand — prompt, approval_mode, model, session_indexGeminiApprovalMode — Default |
AutoEdit |
Yolo |
resolve_gemini_executable() — find_command_on_path("gemini")spawn_gemini_runtime() — spawn CLI with appropriate flags; for stream-json
mode, spawn per-turn; for ACP mode, spawn once as a long-lived processhandle_gemini_stream_event() — parse JSONL events (init, message,
tool_use, tool_result, error, result), map to TermAl message typeshandle_gemini_acp_message() — reuse or share with Cursor ACP
adapterdeliver_turn_dispatch() — add PersistentGemini armdispatch_turn() / start_turn_on_record() — handle Agent::Geminiupdate_session_settings() — handle Gemini approval mode and model selection"gemini" in Agent::from_strUnlike Claude and Codex which are long-lived processes, the stream-json path spawns a new process per turn. This means:
SessionRuntime handle between turns (or use a lightweight “idle” state)--resume latest on each new turnsession_id from the init event for resume trackingThe ACP path avoids this — a single long-lived process like Claude/Codex.
AgentType in types.ts: "Claude" | "Codex" | "Gemini"auto, pro, flash, flash-lite)default, auto_edit, yolo)tool_use / tool_result events to command cards with streaming outputinit, message, tool_use,
tool_result, error, result)--resume--experimental-acp may change or break across Gemini CLI
releases. Should we gate ACP mode behind a TermAl feature flag?tool_use/tool_result provide enough
structure to render diff cards, or do we need to parse patch content from
tool output?--sandbox flag enables a container sandbox. Should
TermAl surface this as a session setting alongside approval mode?