Skip to content

feat(agent): add Gemini as a first-class LLM provider#2053

Open
atishpatel wants to merge 4 commits into
mainfrom
feat/gemini-provider
Open

feat(agent): add Gemini as a first-class LLM provider#2053
atishpatel wants to merge 4 commits into
mainfrom
feat/gemini-provider

Conversation

@atishpatel

Copy link
Copy Markdown
Contributor

Summary

Adds Gemini as a first-class buzz-agent LLM provider using Google's OpenAI-compatible surface, so it reuses the existing Chat Completions request/response path rather than introducing a second serializer/parser.

  • Provider id gemini; env vars GEMINI_API_KEY, GEMINI_MODEL, optional GEMINI_BASE_URL (default https://generativelanguage.googleapis.com/v1beta/openai).
  • Static Authorization: Bearer $GEMINI_API_KEY. API shape pinned to Chat Completions (POST {base}/chat/completions) — never Responses or native generateContent (there is no /responses endpoint on the Google host).
  • BUZZ_AGENT_MODEL overrides GEMINI_MODEL; existing OpenAI/Anthropic/Databricks behavior unchanged.

Changes

Transport (buzz-agent)

  • config.rs: Provider::Gemini variant; resolve_provider + from_env arms read GEMINI_* with the Google default base URL, API pinned to Chat. Extracted GEMINI_DEFAULT_BASE_URL constant.
  • llm.rs: route Gemini through the OpenAI Chat arms + static-bearer token source. Extracted should_use_responses so a Chat-pinned provider can never be routed to /responses regardless of host/auto-upgrade latch.

Desktop (Tauri)

  • agent_models.rs: provider-aware base-URL/key resolution so /models discovery reads GEMINI_BASE_URL/GEMINI_API_KEY with key-redacted errors; Gemini ids skip the GPT text-model filter.
  • readiness.rs: require GEMINI_API_KEY, accept GEMINI_MODEL fallback.

Desktop (frontend)

  • personaDialogPickers.tsx: Gemini provider option, credential mapping, explicit-model requirement, and Goose gating — Gemini is offered/required only for the buzz-agent runtime (Goose has no gemini arm; offering it would demand a GEMINI_API_KEY the Goose backend never reads). A value already persisted with gemini still renders via the (current) tail.
  • bakedEnvHelpers.ts: gemini → GEMINI_MODEL inherited/global model fallback.
  • personaModelDiscoveryStatus.ts: Gemini label + tailored "Enter a Gemini API key to load Gemini models." message.

Docs

  • crates/buzz-agent/README.md: run example, env-var table, Providers table row, and Chat-pinned endpoint note.

Testing

  • cargo test -p buzz-agent → 265 lib + integration suites, 0 failed (incl. chat_pinned_never_uses_responses_regardless_of_host_or_latch); cargo fmt --check clean; cargo clippy --all-targets 0 warnings.
  • Tauri: readiness 53 passed (3 Gemini), agent_models 14 passed (3 Gemini).
  • Frontend: focused suites 180/180; full suite 3127/3127; tsc --noEmit clean; Biome clean; file-size guard PASS.

Adds `gemini` as a first-class buzz-agent provider using Google's
OpenAI-compatible surface, so it reuses the existing Chat Completions
request/response path rather than introducing a second serializer/parser.

Provider contract (RESEARCH/GEMINI_PROVIDER_API_CONTRACT.md):
- Provider id `gemini`; env vars `GEMINI_API_KEY`, `GEMINI_MODEL`, optional
  `GEMINI_BASE_URL` (default https://generativelanguage.googleapis.com/v1beta/openai).
- Static `Authorization: Bearer $GEMINI_API_KEY`; `/models` discovery reuses the
  OpenAI-compatible path with the same bearer and key-redacted errors.
- API shape pinned to Chat Completions — never Responses or native
  generateContent (there is no /responses endpoint on the Google host).

Changes:
- buzz-agent config.rs: `Provider::Gemini` variant, `resolve_provider` +
  `from_env` arms reading GEMINI_* with the Google default base URL, API pinned
  to Chat. Extracted `GEMINI_DEFAULT_BASE_URL` constant.
- buzz-agent llm.rs: route Gemini through the OpenAI Chat arms and the static
  bearer token source. Extracted `should_use_responses` so a Chat-pinned
  provider can never be routed to /responses regardless of host/auto-upgrade.
- desktop agent_models.rs: provider-aware base-URL/key resolution so `/models`
  discovery reads GEMINI_BASE_URL/GEMINI_API_KEY; Gemini ids pass through the
  OpenAI text-model filter unchanged.
- desktop readiness.rs: require GEMINI_API_KEY and accept GEMINI_MODEL fallback.
- frontend personaDialogPickers.tsx: provider option, credential mapping, and
  explicit-model requirement.

Tests: new coverage in config.rs, llm.rs, agent_models_tests.rs, readiness.rs,
and editAgentProviderDiscovery.test.mjs. Full local gates green: buzz-agent +
desktop Tauri unit tests, desktop TS tests, typecheck, biome, clippy
(workspace + Tauri), rustfmt.

Co-authored-by: Atish Patel <atish@squareup.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Atish Patel <atish@squareup.com>
@atishpatel
atishpatel requested a review from a team as a code owner July 17, 2026 23:02
npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta and others added 3 commits July 17, 2026 19:21
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
The neutral "API Key" relabel made Playwright's default non-exact
getByLabel("API Key") resolve to two elements — the credential input
and the "Show API key" visibility toggle button — tripping strict-mode
violations across the persona/agent E2E and smoke specs (Desktop E2E
Integration + all four Desktop Smoke shards).

Pin the selector with { exact: true } so it resolves only to the input,
whose accessible name is exactly "API Key" (the required "*" marker is
aria-hidden and excluded from the accessible name). This also resolves
the Biome format error in onboarding-agent-defaults.spec.ts reported by
Desktop Core.

Signed-off-by: Atish Patel <atish@squareup.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to 4e18170. `getByLabel("API Key", { exact: true })` still
failed: when the credential is required the field's accessible name is
"API Key *" (the required "*" marker is included in the label-derived
name despite aria-hidden), so an exact "API Key" match resolves to zero
elements and times out.

Switch all 16 call sites to getByTestId("persona-provider-api-key") —
the stable id on the input across every surface (persona, global config,
edit agent). This sidesteps both the show/hide-button collision and the
required-marker name variance. Playwright's own strict-mode output named
this test id as the canonical locator for the element.

Signed-off-by: Atish Patel <atish@squareup.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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