feat(provider): add Gemini CLI as a headless observation provider#2764
feat(provider): add Gemini CLI as a headless observation provider#2764pg-adm1n wants to merge 6 commits into
Conversation
Adds GeminiCliProvider, which drives the user-installed `gemini` CLI (@google/gemini-cli) as an observation/summary generation backend — the same role ClaudeProvider plays for the Claude Agent SDK. Unlike GeminiProvider (Gemini REST API + API key), this provider piggybacks on the gemini CLI's existing OAuth login, so users on the free/subscription tier need no API key. Each claude-mem session maps to one native gemini session: the first turn creates it (--session-id <uuid>); later turns resume it (--resume <uuid>), so gemini holds conversation context (and reuses its prompt cache) across separate subprocess invocations. Hardening: --approval-mode plan (read-only, no tool/file side effects), --skip-trust (headless in untrusted dirs), --output-format json (clean JSON on stdout), prompt piped via stdin + -p "" (sidesteps ARG_MAX on large observation payloads). Selected via CLAUDE_MEM_PROVIDER=gemini-cli. Wiring spans worker-service, SessionRoutes, SettingsRoutes, SettingsDefaultsManager, worker-types, install.ts, npx-cli, and paths.ts (new GEMINI_CLI_SESSIONS_DIR). The init turn is a priming turn (observer role + output format, no tool observation yet), so it returns an empty response by design — logged at debug, not error, matching ClaudeProvider's silent handling of the equivalent empty priming response. Verified end-to-end: isolated worker on a custom port driven over HTTP, 2 concurrent sessions -> 4 observations + 2 structured summaries generated by gemini-2.5-flash-lite, with correct type classification and zero error-level log lines.
1e8821c to
28e4c7a
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1e8821c089
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const args: string[] = []; | ||
| if (opts.sessionId) { | ||
| args.push('--session-id', opts.sessionId); | ||
| } else if (opts.resumeId) { | ||
| args.push('--resume', opts.resumeId); |
There was a problem hiding this comment.
Drop unsupported Gemini --session-id flag
Every fresh Gemini CLI session enters this branch, but the current Gemini CLI reference lists --resume/-r for continuing sessions and does not define a --session-id startup option (see https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/cli-reference.md#cli-options). With this flag present, the first headless spawn for any new claude-mem session fails before a session_id can be captured; start without a session flag and use the JSON session_id returned by the CLI for subsequent --resume calls.
Useful? React with 👍 / 👎.
| const child = spawn(opts.executable, args, { | ||
| cwd: opts.cwd, | ||
| env: process.env, | ||
| stdio: ['pipe', 'pipe', 'pipe'], |
There was a problem hiding this comment.
Pass saved Gemini API keys into the subprocess
When the user has no OAuth cache but did save GEMINI_API_KEY through claude-mem's .env, isGeminiCliAvailable() returns true via getCredential('GEMINI_API_KEY'), but this spawn only forwards the worker's current process.env. Worker daemons are started from sanitized shell envs and do not automatically load ~/.claude-mem/.env, so the Gemini CLI child will not see the API key required for headless auth and will fail despite the provider being reported available; merge the loaded credential into the child env before spawning.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR introduces
Confidence Score: 4/5Safe to merge with minor follow-up: the two new findings are non-blocking quality issues, while the more serious silent-fallback concern in SessionRoutes was flagged in a prior review cycle. The core provider implementation — subprocess hardening, stdin error handling, abort/timeout, and the session-not-found re-priming fix — is solid and well-tested. The two new items are: blank-line collapsing in the XML parser that affects all providers (harmless in today's output format but an unintended behaviour change), and a discrepancy between the documented default model and the actual shipped default, which could cause silent failures for users who don't configure the model explicitly. src/sdk/parser.ts (cleanXmlLists side-effect on all providers) and src/shared/SettingsDefaultsManager.ts (default model value). Important Files Changed
Sequence DiagramsequenceDiagram
participant R as SessionRoutes
participant G as GeminiCliProvider
participant F as findGeminiExecutable
participant CLI as gemini CLI (subprocess)
participant DB as SessionStore
R->>R: getSelectedProvider()
R->>G: startSession(session)
G->>F: findGeminiExecutable()
F-->>G: /path/to/gemini
G->>CLI: spawn(gemini --skip-trust --approval-mode plan --output-format json -m model -p "")
Note over G,CLI: init / priming turn (no --resume)
CLI-->>G: "{session_id, response, stats}"
G->>DB: ensureMemorySessionIdRegistered(sessionId)
loop processMessageLoop
G->>CLI: spawn(gemini --resume session_id ...)
alt session still alive
CLI-->>G: "{session_id, response}"
G->>G: processAgentResponse()
else session_not_found error
G->>CLI: spawn fresh (re-prime)
CLI-->>G: "{new_session_id}"
G->>CLI: spawn(gemini --resume new_session_id ...)
CLI-->>G: "{session_id, response}"
end
end
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
src/sdk/parser.ts:214-219
**Blank-line normalisation applied to all fields, not just list fields**
`cleanXmlLists` unconditionally applies `.replace(/\n\s*\n/g, '\n').trim()` at the end of every non-code-block segment, so multi-paragraph field values from any provider — Claude, Gemini REST API, or gemini-cli — will have their blank-line separators silently collapsed. If, for example, a `learned` or `notes` field legitimately returns paragraphs separated by blank lines today, those blank lines are now stripped for every provider. Guard the normalisation so it only runs when list content was actually transformed.
```suggestion
.join('\n');
// Only collapse blank lines when list content was actually transformed,
// to avoid corrupting intentional paragraph spacing in non-list fields.
return hasListContent ? processed.replace(/\n\s*\n/g, '\n').trim() : processed;
});
return processedParts.join('');
```
### Issue 2 of 2
src/shared/SettingsDefaultsManager.ts:101
**Default model `'auto'` disagrees with PR description and may be invalid**
The PR description states the default is `gemini-2.5-flash-lite`, but the code ships `'auto'` — which is then passed literally as `-m auto` to the gemini CLI. The gemini CLI's `-m` flag expects a model identifier (e.g. `gemini-2.5-flash-lite`); if `auto` is not a recognised value, every installation that hasn't explicitly set `CLAUDE_MEM_GEMINI_CLI_MODEL` will fail on the very first turn with a non-zero exit code. The E2E test in the PR description ran with `gemini-2.5-flash-lite`, not `auto`, so this path may be untested. If the CLI does support `auto` as a sentinel, a brief comment explaining its semantics would prevent future confusion.
Reviews (6): Last reviewed commit: "fix(gemini): prevent prose pollution by ..." | Re-trigger Greptile |
|
I WANT THIS!! WE NEED THIS! <3 Great Job! |
|
Any chance you can try the same with Codex? |
The viewer Settings modal hardcoded only claude/gemini/openrouter in the AI Provider dropdown, so gemini-cli — already supported by the installer, worker, backend validation, and settings defaults — could not be selected from the UI. - Add the "Gemini CLI" option to the AI Provider dropdown - Add a gemini-cli config block: model, optional binary path, request timeout (no API key; uses the gemini CLI OAuth login) - Add CLAUDE_MEM_GEMINI_CLI_MODEL/_PATH/_TIMEOUT_MS to the viewer Settings type so the new fields type-check - Rebuild viewer-bundle.js
When a gemini CLI session vanished mid-run, runTurn cleared the stale memorySessionId and spawned a fresh session but re-sent the current observation/summary prompt directly. A brand-new session never received the init turn (observer role + structured output format), so the model emitted free-form text that processAgentResponse silently discarded — every recovery after a mid-run session expiry lost its observations. runTurn now primes the fresh session with buildContinuationPrompt (the same priming the normal init->turn flow uses), then resumes it with the real prompt. Extracted runFreshTurn to share the spawn/capture/register logic between the first-turn and recovery paths. Added a recovery-path test asserting the failed-resume -> fresh re-prime -> resume sequence. Also address two reviewer nits: - remove dead getActiveAgent() in SessionRoutes: routing goes through getSelectedProvider / startGeneratorWithProvider; the method was private and uncalled, and carried a divergent error-on-unavailability contract that would surprise anyone who wired it back in. - fix the misleading "leaving queue intact" empty-response log: the message was already consumed from the iterator, so nothing is retried. Rebuilt plugin/scripts/worker-service.cjs.
|
@thedotmack I had build one with opencli+chatgpt web, but I got bot detention alert, and cloudflare come in. |
…uto model in gemini-cli
Summary
Adds
GeminiCliProvider— a new observation/summary generation backend that drives the user-installedgeminiCLI (@google/gemini-cli), the same roleClaudeProviderplays for the Claude Agent SDK.Unlike
GeminiProvider(which calls the Gemini REST API and needs aGEMINI_API_KEY), this provider piggybacks on the gemini CLI's existing OAuth login, so users on the free/subscription tier need no API key. Selected viaCLAUDE_MEM_PROVIDER=gemini-cli.How it works
Each claude-mem session maps to one native gemini session:
gemini --skip-trust --approval-mode plan --output-format json -m <model> -p ""(prompt piped on stdin).session_idreturned by the CLI:gemini --resume <uuid> ..., so gemini holds conversation context (and reuses its prompt cache) across separate subprocess invocations. The captured UUID is persisted as the session'smemory_session_id.Hardening:
--approval-mode plan→ read-only; the model runs no tools and writes no files (pure text generation, no disk side effects).--skip-trust→ required for headless runs in untrusted dirs.--output-format json→ clean{ session_id, response, stats }on stdout (warnings go to stderr and are ignored).-p ""→ flips the CLI into headless mode and sidestepsARG_MAXon large observation payloads.GEMINI_API_KEYfrom claude-mem's.envis forwarded to the subprocess when the worker env does not already provide one.The init turn is a priming turn (observer role + output format only, no tool observation yet), so it returns an empty response by design — logged at
debug, noterror, matchingClaudeProvider's silent handling of the equivalent empty priming response.Wiring
GeminiCliProvider.ts+find-gemini-executable.ts(new), plus the standard provider wiring points:worker-service.ts,SessionRoutes.ts,SettingsRoutes.ts,SettingsDefaultsManager.ts,worker-types.ts,install.ts,npx-cli/index.ts, andpaths.ts(newGEMINI_CLI_SESSIONS_DIR). Rebuiltplugin/scripts/*.cjsbundles included.New settings:
CLAUDE_MEM_GEMINI_CLI_MODEL(defaultgemini-2.5-flash-lite),CLAUDE_MEM_GEMINI_CLI_PATH(empty = auto-detect),CLAUDE_MEM_GEMINI_CLI_TIMEOUT_MS.Test plan
npm run typecheck— clean.bun test tests/gemini-cli-provider.test.ts— covers fresh-session spawn args, saved API key forwarding, and CLI path cache invalidation.bun test tests/worker/agents/— 49 pass / 0 fail / 4 skip.bun test— 2064 pass / 0 fail / 18 skip.CLAUDE_MEM_DATA_DIR=… CLAUDE_MEM_WORKER_PORT=… CLAUDE_MEM_PROVIDER=gemini-cli) and drove 2 concurrent sessions over HTTP (/api/sessions/{init,observations,summarize}). Result: 4 observations + 2 structured summaries generated bygemini-2.5-flash-lite, with correct type classification (discovery/feature/bugfix),factsextraction, andfiles_modifiedtracking — zero error-level log lines.