Files
roboco/alembic/versions/085_seed_gemini_provider.py
21d6730400 feat(providers): Gemini CLI provider — ModelProvider.GEMINI (#660)
* feat(providers): Gemini CLI provider — ModelProvider.GEMINI

Mirrors the grok blueprint with source-verified divergences (all facts
pinned against google-gemini/gemini-cli @ 9681621c): no refresher
daemon — Google's refresh tokens are reusable, so the RO host mount is
COPIED to a writable container-local ~/.gemini and each container
refreshes in-process independently (the write-back crash risk on RO
never triggers); settings.json renders security.auth.selectedType
'oauth-personal', experimental.enableAgents=false (subagent ban),
autoConfigureMemory=false with a bounded heap; tool scoping rides the
tiered TOML Policy Engine (deny-only rules that yolo mode structurally
cannot beat); gemini -p with --output-format stream-json; usage parsed
from the run's own stdout stats — the adversarial pass caught the
parser reading the json-mode nested shape while the entrypoint runs
stream-json's FLAT shape (every real run would have priced $0 forever,
hidden by fixtures sharing the assumption) — now flat-primary with the
nested shape as cited fallback; rate-limit classified from structured
error.type only (model-echo immune), native exit 41 auth passthrough;
per-model pricing for the three GA models; migrations 084 (enum) + 085
(seed) complete the 082-085 finale chain. V1 excludes interactive
intake/secretary. Stack-merge required two behavior-preserving
complexity refactors in the shared park/usage plumbing (a park-pair
loop; a usage-reader dispatch dict).

* fix(providers): route gemini usage read through the containment barrier

Mirrors the codex/grok fix — _gemini_usage_json now delegates to
_read_usage_json_contained, so CodeQL's path-injection alert on the
gemini read is resolved by the same resolve-and-contain guard.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-23 03:53:21 +02:00

71 lines
2.3 KiB
Python

"""Idempotently seed the Gemini (Google) provider row.
The ``modelprovider`` enum carries ``'gemini'`` as of migration 084. This
migration seeds the corresponding ``provider_configs`` row so the Settings UI
can list it for role/agent model assignment.
Unlike Grok's row (migration 039), Gemini has no API-key mode: the CLI
authenticates from a mounted OAuth credential (``~/.gemini/oauth_creds.json``),
never a base URL / bearer token, so both columns stay NULL permanently. The
row starts disabled; an operator enables it once the host OAuth credential is
in place (``ROBOCO_HOST_GEMINI_DIR``). ON CONFLICT (name) DO NOTHING keeps
this safe to re-run.
RE-CHAIN CAVEAT: renumbered 083->085 (was 083 in this task's original
checkout at head 081) to merge after two sibling worktrees' 082 (routing) /
083 (codex `seed_openai_provider`) — see 084_modelprovider_gemini.py's
docstring for the full note. Neither sibling exists in this checkout, so this
worktree's own migration-graph-integrity / enum-parity tests fail on the
missing revisions until the real three-way merge lands.
Revision ID: 085_seed_gemini_provider
Revises: 084_modelprovider_gemini
Create Date: 2026-07-23
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
revision = "085_seed_gemini_provider"
down_revision = "084_modelprovider_gemini"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute(
sa.text(
"""
INSERT INTO provider_configs
(id, name, type, base_url, auth_token_encrypted, enabled, created_at)
VALUES
(
gen_random_uuid(),
'Gemini (Google)',
'gemini',
NULL,
NULL,
false,
now()
)
ON CONFLICT (name) DO NOTHING
"""
)
)
def downgrade() -> None:
# Drop model_assignments pointing at the Gemini row first to avoid a FK
# RESTRICT violation on provider_configs.id.
op.execute(
sa.text(
"DELETE FROM model_assignments "
"WHERE provider_config_id IN ("
" SELECT id FROM provider_configs WHERE name = 'Gemini (Google)'"
")"
)
)
op.execute(sa.text("DELETE FROM provider_configs WHERE name = 'Gemini (Google)'"))