@gullabs/google
v0.12.0
Published
Gemini provider adapter for any-llm — Flex tier, thinking capture, structured output, error classification.
Maintainers
Readme
@gullabs/google
Gemini provider adapter for any-llm. A thin mapping layer over @google/genai that converts ResolvedRequest → Gemini SDK params and maps the response back to AdapterResult. Never persists, never computes cost, never loops — pure request/response.
Install
pnpm add @gullabs/google @gullabs/core @google/genaiPeer dependency: @google/genai ^1 || ^2
Key exports
| Export | What it is |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| googleProvider(opts?) | ProviderPlugin factory — bundles the adapter, model descriptors, and pricing source |
| geminiAdapter(opts?) | Creates the ProviderAdapter for Gemini |
| GeminiAdapterOptions | { client?: GeminiClientLike } — inject a pre-built or fake client |
| GeminiClientLike | Structural interface the adapter depends on (satisfied by real SDK and fakes) |
| buildGoogleClient(auth) | Builds the real @google/genai client from AuthMaterial |
| isGeminiCapacityError(err) | Detects Gemini Flex shared-capacity errors for built-in fallback |
| geminiModelDescriptors, gemmaModelDescriptors, defaultGeminiRegistry | Built-in model descriptors + pre-built registry |
| geminiPricingSource(), GEMINI_PRICING, TIER_FACTOR | Built-in Gemini pricing snapshot and tier-factor map |
| GoogleFileStore | Files API: upload + poll ACTIVE + delete |
| FileDeleteOptions | { failClosed?, signal? } — opt-in fail-closed delete (parity with @gullabs/xai) |
File store delete modes
GoogleFileStore.delete defaults to fail-open (errors → onDeleteError, resolve). Pass { failClosed: true } when the host gates durable state on known success; HTTP/SDK not-found remains success (idempotent). Empty handle.name always throws bad_request.
await store.delete(handle) // fail-open
await store.delete(handle, { failClosed: true }) // throw on non-not-found failureQuick example
import { createClient, composeProviders } from '@gullabs/core'
import { googleProvider } from '@gullabs/google'
const client = createClient({
...composeProviders([googleProvider()]),
})
// Auth is required per call — the library never reads environment variables.
const result = await client.generate(
{
provider: 'google',
model: 'gemini-2.5-flash',
messages: [{ role: 'user', parts: [{ kind: 'text', text: 'Hello' }] }],
},
{ auth: { apiKey: 'YOUR_GEMINI_API_KEY' } },
)What it maps
serviceTier: 'flex'→ Gemini Flex service tier when the model descriptor supports it- omitted
serviceTier→ provider-default request behavior reasoning.includeThoughts→thinkingConfig.includeThoughts; thought parts becomereasoningTextreasoning.effort→thinkingBudget(Gemini 2.5) orthinkingLevel(Gemini 3 / Gemma 4)reasoning.budgetTokens→ admitted only on Gemini 2.5 budget-api models; strict descriptors reject it on level-api modelsoutput.jsonSchema→responseMimeType: 'application/json'+ verbatimresponseSchemawhen native structured output is enabled; the engine returns parsed output andoutputParsedwithout validating shapeproviderOptions.google.*→ typed provider-extension lane for admitted keys such ascachedContent,safetySettings, and exact tool declarations- Usage:
promptTokenCount→inputTokens,candidatesTokenCount+thoughtsTokenCount→outputTokens(GROSS) - Errors:
401and a bare403default toinvalid_auth;429→rate_limited;5xx→server; timeouts; Gemini safety blocks are a 200-pathcontent_filter(promptFeedback.blockReason/ no candidates), not an HTTP 403
Strict model-config expectations
This adapter expects config that has already been parsed through the selected descriptor boundary:
descriptor.configSchemais the runtime source of truth.descriptor.configJsonSchemais the derived form/UI schema.providerOptions.googleis not a caller-wins override lane forserviceTier, sampling, reasoning, or response schema.prioritystays rejected even though Google documents it, because the library has not yet shipped the matching schema, pricing, served-tier recording, and tests.
For structured output with built-in tools, follow the exact public
generateContent evidence: the current docs only admit that combination for
gemini-3.1-pro-preview and gemini-3.5-flash. Other models should fail early
instead of relying on adapter repair or provider-side surprises.
Gemma 4
The default registry includes two API-verified Gemma 4 models: gemma-4-31b-it
and gemma-4-26b-a4b-it. Both route through this adapter and support:
- Native structured output —
responseMimeType+ verbatimresponseSchemaare sent automatically whenoutput.jsonSchemais set. - Grounding —
tools:[{googleSearch:{}}]viaproviderOptions.google. - Vision —
inline-mediaandfile-urimultimodal message parts. - Thinking —
reasoning.effortmaps tothinkingLevel(reasoningApi: 'level'). Gemma 4 thinking is binary: onlyeffort: 'none'(MINIMAL) andeffort: 'high'(HIGH) are accepted. Passingeffort: 'low'oreffort: 'medium'is rejected at validation time with abad_requesterror because the model only supports MINIMAL and HIGHthinkingLevelvalues. Note:thinkingBudgetis not supported (rejected by the API with HTTP 400). - Tunable sampling —
temperature,topP,topKare accepted.
This follows the library-wide reject, don't map rule: unsupported or incorrect input throws a
typed bad_request LlmError at validation time rather than being silently clamped or coerced
into something the model happens to accept.
Gemma 4 models are intentionally unpriced (cost.microUsd will be null), and
the strict contract does not admit any serviceTier for them until the public
docs and live evidence line up on that field.
Learn more
- Monorepo root README — full architecture, auth model, and package overview
docs/grounded-structured.md— the recommended two-call Gemini grounding → structured-output recipedocs/multi-runtime.md— web route + Temporal worker integration pattern, auth, metadata, and retry ownership@gullabs/coreREADME — engine, ports, and theLlmErrorcontract
