Gemini CLI Adaptation Guide For OpenASE
April 4, 2026 ยท View on GitHub
This guide describes how Gemini CLI should be integrated into OpenASE with a precise, protocol-first approach.
It follows the same design principles used for Claude Code integration:
- Define explicit protocol types
- Separate upstream fields from OpenASE-derived semantics
- Document which layer each field comes from
- Lock the behavior with protocol-level tests
Scope
This guide is about Gemini CLI in headless mode, especially:
--output-format json--output-format stream-json
Relevant upstream references:
references/gemini-cli/docs/cli/headless.mdreferences/gemini-cli/packages/core/src/output/types.tsreferences/gemini-cli/packages/core/src/agent/types.tsreferences/gemini-cli/packages/core/src/agent/event-translator.tsreferences/gemini-cli/packages/cli/src/nonInteractiveCliAgentSession.tsreferences/gemini-cli/docs/reference/tools.md
Current OpenASE State
OpenASE currently uses Gemini in a very thin one-shot mode:
Current behavior:
- invokes
gemini -p ... --output-format json - waits for the whole process to exit
- parses only final
responseandstats - emits:
- normalized assistant message
- final
done
What is missing today:
- no streaming deltas
- no
init - no tool call events
- no tool result events
- no warning/error event granularity
- no per-turn session metadata beyond OpenASE-local session bookkeeping
- no interruption / elicitation modeling
This is the main gap if we want Gemini to approach Claude/Codex parity.
Upstream Layering
Gemini CLI has three distinct layers.
1. Internal model/runtime events
Internal Gemini agent logic emits ServerGeminiStreamEvent values such as:
ModelInfoContentThoughtCitationToolCallRequestToolCallResponseErrorFinishedUserCancelledMaxSessionTurnsAgentExecutionStoppedAgentExecutionBlockedInvalidStream
Reference:
These are not the CLI transport contract. They are internal.
2. Internal agent protocol
Gemini translates internal events into AgentEvent values such as:
initializesession_updatemessageagent_startagent_endtool_requesttool_responsetool_updateusageerrorelicitation_requestelicitation_responsecustom
Reference:
Important detail:
tool_updateusagesession_updateelicitation_request
exist internally even if they do not all survive into the final headless stream-json transport.
3. Headless CLI transport contract
The final CLI stream-json contract is narrower and is what OpenASE should treat as the upstream wire protocol if it keeps using the Gemini CLI binary.
Reference contract:
Event types:
initmessagetool_usetool_resulterrorresult
This is the correct parse boundary for OpenASE if we integrate Gemini through the CLI process.
Exact Headless Transport DTOs
The upstream stream-json DTOs are explicit.
init
{
"type": "init",
"timestamp": "ISO-8601",
"session_id": "string",
"model": "string"
}
Semantics:
- starts the headless stream
- identifies the Gemini session
- gives the selected model
message
{
"type": "message",
"timestamp": "ISO-8601",
"role": "user | assistant",
"content": "string",
"delta": true
}
Notes:
deltais optional- in streaming mode, assistant text is emitted incrementally with
delta: true - user messages are also emitted once by the CLI wrapper
tool_use
{
"type": "tool_use",
"timestamp": "ISO-8601",
"tool_name": "string",
"tool_id": "string",
"parameters": {}
}
Semantics:
- exact tool invocation request
tool_idis the correlation key for the latertool_result
tool_result
{
"type": "tool_result",
"timestamp": "ISO-8601",
"tool_id": "string",
"status": "success | error",
"output": "string",
"error": {
"type": "string",
"message": "string"
}
}
Important limitation:
- the headless transport only preserves a string
output - it does not preserve the richer internal
tool_response.displayContent,content, anddatastructure - if OpenASE needs Claude/Codex-level fidelity for diffs, structured file outputs, or media results, direct CLI
stream-jsonis already a lossy boundary
error
{
"type": "error",
"timestamp": "ISO-8601",
"severity": "warning | error",
"message": "string"
}
Semantics:
- non-fatal warnings and surfaced runtime issues
- emitted for things like loop detection or non-fatal blocked execution
result
{
"type": "result",
"timestamp": "ISO-8601",
"status": "success | error",
"error": {
"type": "string",
"message": "string"
},
"stats": {
"total_tokens": 0,
"input_tokens": 0,
"output_tokens": 0,
"cached": 0,
"input": 0,
"duration_ms": 0,
"tool_calls": 0,
"models": {}
}
}
Semantics:
- terminal event for the headless run
- carries aggregated usage
modelscontains per-model breakdown
What The CLI Drops
This matters a lot for design decisions.
The upstream non-interactive wrapper explicitly ignores several internal AgentEvent kinds:
initializesession_updateagent_starttool_updateelicitation_requestelicitation_responseusagecustom
Reference:
So if OpenASE consumes only headless CLI stream-json, it cannot recover:
- internal thought/citation metadata beyond what becomes plain assistant text
- rich tool update progress
- explicit usage events before final result
- structured elicitation / approval requests
- custom events
That loss is upstream behavior, not an OpenASE parser bug.
Tool Mapping Guidance
Gemini publishes exact built-in tool names in its tools reference:
run_shell_commandglobgrep_searchlist_directoryread_fileread_many_filesreplacewrite_fileask_userwrite_todosactivate_skillget_internal_docssave_memoryenter_plan_modeexit_plan_modecomplete_taskgoogle_web_searchweb_fetch
Reference:
OpenASE should map these with exact allowlists, not fuzzy string matching.
Recommended derived categories:
- command tool:
run_shell_command
- file read tools:
read_fileread_many_fileslist_directoryglobgrep_search
- file write tools:
replacewrite_file
- ask-user / interrupt tools:
ask_user
- search/fetch tools:
google_web_searchweb_fetch
- planning / bookkeeping tools:
write_todosenter_plan_modeexit_plan_modecomplete_tasksave_memoryactivate_skillget_internal_docs
Recommended OpenASE Parse Layer
If OpenASE keeps integrating Gemini via CLI, add a dedicated protocol file similar to Claude:
internal/chat/gemini_protocol.go
Suggested explicit types:
geminiCLIInitEventgeminiCLIMessageEventgeminiCLIToolUseEventgeminiCLIToolResultEventgeminiCLIErrorEventgeminiCLIResultEventgeminiCLIStreamStatsgeminiCLIModelStats
Design rules:
- Parse exact transport DTOs first
- Keep upstream field names recognizable
- Only after parsing, derive OpenASE semantics
Example derived semantics:
tool_use(tool_name=run_shell_command)-> command tool requesttool_result(tool_id=...)paired with previousrun_shell_command-> command output blocktool_resultpaired withreplaceorwrite_file-> file mutation resulttool_result.outputthat looks like a unified diff -> derived diff event
That last item is still heuristic and must be documented as derived, not upstream-native.
Recommended OpenASE Runtime Mapping
For chat mode
Prefer --output-format stream-json over json.
Map as follows:
init- emit session metadata / thread anchor update
message(role=user)- usually ignore for transcript if OpenASE already owns the user message
- optionally preserve as trace for perfect replay fidelity
message(role=assistant, delta=true)- emit assistant snapshot/delta stream
tool_use- emit tool call event
tool_result- emit tool result event
- if paired tool is
run_shell_command, derive command output - if paired tool is
replace/write_file, derive mutation summary
error- emit warning/error trace
result- emit final usage + completion/failure
For ticket/runtime mode
If Gemini is ever used in orchestrated ticket runs, mirror the Claude split:
- protocol layer parses exact Gemini headless events
- adapter layer maps them into:
OutputProducedToolCallRequestedTurnDiffUpdatedTokenUsageUpdatedTurnCompletedTurnFailedTaskStatusonly when the transport truly carries status-like information
Do not invent fake phase/task events that Gemini CLI never emitted.
Current Gap Analysis Against This Guide
OpenASE today:
- uses
--output-format json - parses final
response - parses final usage from
stats - misses all streaming tool and warning/error events
To align with the upstream protocol, OpenASE should:
- add
stream-jsonmode support - parse exact Gemini headless transport DTOs
- track
tool_id -> tool_namecorrelation - preserve final
result.stats - keep derived semantics explicit and allowlist-based
Testing Strategy
Tests should be written at three layers.
1. Protocol parser tests
Add exact JSON fixture tests for:
init- assistant
messagedelta tool_usetool_resultsuccesstool_resulterrorerrorresultsuccessresulterror
These tests should assert exact fields, not just "no error".
2. Runtime mapping tests
Add mapping tests for:
run_shell_commandtool use/result -> command outputreplace/write_filetool use/result -> mutation tracegoogle_web_search/web_fetch-> tool call cards, not command cards- final
result.stats-> usage event
3. Regression tests for known edge cases
From upstream tests and docs:
- assistant deltas arrive in multiple chunks
- cancelled tool calls may still surface as
tool_result.status = successin stream-json legacy parity mode- reference:
nonInteractiveCliAgentSession.test.ts:2378
- reference:
- warning events may continue before final success result
Recommended Implementation Order
- Introduce
gemini_protocol.gowith exact transport DTOs - Add a Gemini stream parser that reads JSONL events
- Switch chat runtime from
--output-format jsontostream-json - Keep the current JSON parser as fallback only if stream-json is unavailable
- Add tool-id correlation and derived semantic mapping
- Add protocol and runtime tests
- Only then consider whether a richer non-CLI integration is needed
Final Recommendation
If the goal is "precise and complete" integration through the Gemini CLI binary, OpenASE should treat stream-json as the authoritative wire contract.
If the goal is "Claude/Codex-level full fidelity", note that Gemini CLI headless transport is already lossy relative to Gemini's internal AgentEvent layer. In that case, the next step would be a richer integration boundary than CLI stream-json.