Skip to content

feat: gemini-cli as a codeoid backend over ACP (bundled, Google-subscription auth) - #143

Merged
saucam merged 2 commits into
mainfrom
feat/acp-provider
Jul 10, 2026
Merged

feat: gemini-cli as a codeoid backend over ACP (bundled, Google-subscription auth)#143
saucam merged 2 commits into
mainfrom
feat/acp-provider

Conversation

@saucam

@saucam saucam commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What

The fourth native harness backend: Google's gemini-cli, driven over ACP (Agent Client Protocol — the standardized editor↔agent JSON-RPC/stdio protocol; gemini-cli exposes it via its stable --acp flag). Auth is the user's Google-account OAuth living in ~/.gemini (free tier / AI Pro / Code Assist) — the same subscription posture as claude and codex; codeoid never touches tokens. Design + spike findings: docs/provider-gemini-acp-design.md (notably: there is no official Antigravity SDK — the community package by that name is an IDE-extension kit pointing the wrong way; ACP is the sanctioned machine surface).

Design

  • Shared transport extracted: the codex JSON-RPC client generalizes into providers/jsonrpc-stdio.ts (StdioJsonRpcProcess); codex/rpc.ts is now a thin wrapper baking in the app-server subcommand. All codex tests pass unchanged — same wire behavior, one implementation.
  • GeminiAcpProvider (acp/index.ts), deliberately protocol-thin so any ACP-speaking agent can become a backend with a different binary + registry entry:
    • session/new {cwd} → backing session id; session/prompt is the long-running request whose result is the turn boundary (stopReasonturn_done; usage reported as honest zeros — ACP carries none).
    • session/updateagent_message_chunk/agent_thought_chunk stream as text/thinking deltas; tool_call/tool_call_update become tool records (ACP tool kinds map to codeoid names: execute→Bash, read→Read, …).
    • session/request_permissioncanUseTool — deny picks the agent's reject option; fail-closed throughout: no gate wired or no reject option offered → cancelled; fs/* and terminal/* client capabilities are declined at initialize (gemini-cli falls back to its own tools, all permission-gated) and any stray server request is refused, not guessed.
    • session/cancel → interrupt (turn resolves stopReason: "cancelled" — covered by a test).
  • Bundled like pi: @google/gemini-cli@0.50.0 pinned as an optionalDependency (pure JS — runs under the daemon's own runtime, proven by a live smoke test). Resolution: config → PATH → bundled, with the feat: bundle pi so the backend works with zero install #141 supported-but-unavailable hint path. buildGeminiCliEnv allowlist (GEMINI_/GOOGLE_/LC_ + *_API_KEY; ~/.gemini via HOME).
  • Provider id gemini-cli, distinct from the stateless gemini API backend. No wire changes.

Tests

fake-acp fixture models the ACP shape faithfully — session/prompt stays pending until the turn script finishes, permission requests carry allow/reject options, cancellation flips the hanging turn. A1–A9 cover streaming + stopReason, permission allow/deny round-trips, non-gated tool pairs, the structured history seed (one-shot), interrupt-as-cancel, fail-closed unknown requests, missing binary, and lifecycle; plus resolution order, registry activation/unavailable/disabled, and the live bundled smoke. Coverage checked locally before pushing this time: acp/index.ts 100% lines, resolve.ts 93%, shared transport 99%.

Known limits (in the design doc): no session/load resume yet (the canonical-history seed carries context instead), no model catalog (ACP has none — gemini-cli picks from its own config), plan/mode updates ignored for now. A live smoke needs a Google-logged-in gemini-cli.

bun run test (1267 pass), typecheck, lint green.

🤖 Generated with Claude Code

…ription auth)

Fourth native harness backend: Google's gemini-cli driven over ACP (the
Agent Client Protocol — standardized editor<->agent JSON-RPC/stdio;
gemini-cli exposes it via the stable --acp flag). Auth is the user's
Google-account OAuth in ~/.gemini (free tier / AI Pro / Code Assist) —
subscription posture like claude/codex; codeoid never touches tokens.

- providers/jsonrpc-stdio.ts: the codex transport generalized into a
  shared StdioJsonRpcProcess (codex/rpc.ts is now a thin wrapper baking
  in the app-server subcommand — all codex tests unchanged).
- acp/index.ts: GeminiAcpProvider — session/new is the backing session,
  session/prompt resolves at turn end (stopReason -> turn_done; honest
  zero usage, ACP carries none), session/update chunks -> text/thinking
  deltas + tool_call/tool_call_update -> tool records,
  session/request_permission -> canUseTool (deny picks the reject
  option; fail-closed: no gate or no reject option -> cancelled;
  fs/terminal capabilities DECLINED at initialize and any stray request
  refused), session/cancel -> interrupt. Deliberately protocol-thin: any
  ACP agent can reuse the shape with a different binary.
- Bundled: @google/gemini-cli@0.80->0.50.0 pinned optionalDependency
  (pure JS, runs under the daemon runtime — live smoke in tests);
  resolution config -> PATH -> bundled, with the #141 unavailable-hint
  path. buildGeminiCliEnv allowlist (GEMINI_/GOOGLE_/LC_ + *_API_KEY).
- Config providers.geminiCli {enabled, command}; provider id gemini-cli
  (distinct from the stateless gemini API backend). No wire changes.

Tests: fake-acp fixture (long-running session/prompt answered at turn
end, permission options, cancellation) driving A1-A9 + resolution/
registry states + live bundled smoke. Registry catalog test updated for
the bundled backend.
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.40566% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.81%. Comparing base (305e403) to head (4a60413).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/daemon/providers/registry.ts 70.37% 8 Missing ⚠️
src/daemon/providers/acp/resolve.ts 93.54% 2 Missing ⚠️
src/daemon/providers/jsonrpc-stdio.ts 99.09% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #143      +/-   ##
==========================================
+ Coverage   82.55%   82.81%   +0.25%     
==========================================
  Files         101      104       +3     
  Lines       17404    17722     +318     
==========================================
+ Hits        14368    14676     +308     
- Misses       3036     3046      +10     
Flag Coverage Δ
daemon 82.81% <97.40%> (+0.25%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/config.ts 91.77% <100.00%> (+0.12%) ⬆️
src/daemon/providers/acp/index.ts 100.00% <100.00%> (ø)
src/daemon/providers/codex/rpc.ts 100.00% <100.00%> (+0.92%) ⬆️
src/daemon/providers/env.ts 100.00% <100.00%> (ø)
src/daemon/providers/jsonrpc-stdio.ts 99.09% <99.09%> (ø)
src/daemon/providers/acp/resolve.ts 93.54% <93.54%> (ø)
src/daemon/providers/registry.ts 90.12% <70.37%> (-3.96%) ⬇️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread src/daemon/providers/jsonrpc-stdio.ts
@github-actions

Copy link
Copy Markdown

🤖 Gemini code review

The implementation of the Gemini ACP provider is very well-structured and generic. However, a potential reliability issue was identified in the shared stdio JSON-RPC transport where synchronous write errors on a destroyed stdin stream can crash the host daemon.

Findings: 🔴 0 · 🟠 1 · 🟡 0 · 🟢 0

Ignored 1 file(s) via ignore globs.


Tokens spent · ⬆️ Input: 18,980 · ⬇️ Output: 285 · Σ Total: 26,663
Total may be higher due to thinking token counts.

…i review)

write() on a destroyed stream throws synchronously — the #exited flag
and the stdin error listener only cover the async paths. All frame
writes now go through a #write helper that swallows the throw (exit
handler owns diagnostics) and routes request-path write errors to the
pending rejection.
@saucam
saucam merged commit 7a240a5 into main Jul 10, 2026
4 checks passed
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