feat(agent): add Gemini as a first-class LLM provider#2053
Open
atishpatel wants to merge 4 commits into
Open
Conversation
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Gemini as a first-class
buzz-agentLLM provider using Google's OpenAI-compatible surface, so it reuses the existing Chat Completions request/response path rather than introducing a second serializer/parser.gemini; env varsGEMINI_API_KEY,GEMINI_MODEL, optionalGEMINI_BASE_URL(defaulthttps://generativelanguage.googleapis.com/v1beta/openai).Authorization: Bearer $GEMINI_API_KEY. API shape pinned to Chat Completions (POST {base}/chat/completions) — never Responses or nativegenerateContent(there is no/responsesendpoint on the Google host).BUZZ_AGENT_MODELoverridesGEMINI_MODEL; existing OpenAI/Anthropic/Databricks behavior unchanged.Changes
Transport (
buzz-agent)config.rs:Provider::Geminivariant;resolve_provider+from_envarms readGEMINI_*with the Google default base URL, API pinned to Chat. ExtractedGEMINI_DEFAULT_BASE_URLconstant.llm.rs: route Gemini through the OpenAI Chat arms + static-bearer token source. Extractedshould_use_responsesso a Chat-pinned provider can never be routed to/responsesregardless of host/auto-upgrade latch.Desktop (Tauri)
agent_models.rs: provider-aware base-URL/key resolution so/modelsdiscovery readsGEMINI_BASE_URL/GEMINI_API_KEYwith key-redacted errors; Gemini ids skip the GPT text-model filter.readiness.rs: requireGEMINI_API_KEY, acceptGEMINI_MODELfallback.Desktop (frontend)
personaDialogPickers.tsx: Gemini provider option, credential mapping, explicit-model requirement, and Goose gating — Gemini is offered/required only for thebuzz-agentruntime (Goose has nogeminiarm; offering it would demand aGEMINI_API_KEYthe Goose backend never reads). A value already persisted withgeministill renders via the(current)tail.bakedEnvHelpers.ts:gemini → GEMINI_MODELinherited/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 --checkclean;cargo clippy --all-targets0 warnings.readiness53 passed (3 Gemini),agent_models14 passed (3 Gemini).tsc --noEmitclean; Biome clean; file-size guard PASS.