/** * googleGenAI — the one place a `GoogleGenAI` client gets built (9.13.0). * * Two adapters reach for the same SDK client — `gemini()` (the LLM provider) * and `geminiEmbedder()` — and they need the SAME two doors, the SAME refusals * and the SAME `_client` seam. Duplicating that is how the two drift: one grows * a fix, the other keeps the bug, and the difference is invisible until a * consumer hits the door that was not fixed. * * So the connection lives here and the adapters own only their own calls. * Nothing in this file knows what a model, a message or a vector is. * * ─── The two doors ────────────────────────────────────────────────── * * • **Vertex** — `{ project, location }` → `new GoogleGenAI({ vertexai: true, * project, location, googleAuthOptions })`. Credentials come from * Application Default Credentials: `GOOGLE_APPLICATION_CREDENTIALS`, the * gcloud user credentials, or the metadata server on GCE / Cloud Run. There * is no API key on this path. * • **Gemini API (AI Studio)** — `{ apiKey }` → `new GoogleGenAI({ apiKey })`. * One key, no cloud project. * * ─── The API version, which is not what you would guess ───────────── * * The SDK's DEFAULT API version is `v1beta1` on Vertex and `v1beta` on the * Gemini API — verified against @google/genai 2.16.0 and pinned by * `test/adapters/google/google-surface-pin.test.ts`. It is not `v1`. Callers * who need a specific version pass `apiVersion`; callers who do not at least * find the truth stated instead of assuming a GA path. * * ─── The key can be a CALLBACK, and why (9.29.0) ──────────────────── * * `GoogleGenAIOptions.apiKey` is `string` on the installed SDK (@google/genai * 2.16.0 `genai.d.ts`) — one value, fixed at construction. That is fine for an * AI Studio key, which does not expire, and wrong for every credential that * does: a Vertex OAuth access token lives about an hour, and a key pulled from * a secret manager is rotated on somebody else's schedule. * * An independent field trial on live GCP measured exactly that boundary: an * OAuth token that worked returned HTTP 401 once expired, with no place in the * options to put a fresh one ("Part 2B — ADC refresh versus OpenAI-compatible * OAuth"). So `apiKey` widens to `string | (() => string | Promise)`, * and the refresh boundary is stated rather than implied: * * • The callback runs ONCE PER REQUEST, before the request is built. * • The SDK client is REBUILT only when the returned string differs from the * one already in hand — a callback that returns a cached token costs one * function call and nothing else. * • A stream keeps the key it STARTED with. The boundary is the call, not * the chunk; nothing here can re-authenticate a socket that is already * open. * • Vertex needs none of this. ADC refreshes itself, which the same trial * verified by forcing an in-memory expiry and completing the next call. */ /** * A key, or a way to get one. * * The function form is called before every request — see the file header for * the refresh boundary, which is the CALL and not the chunk. */ export type GoogleApiKeySource = string | (() => string | Promise); /** The connection half of a Google adapter's options — the same on both. */ export interface GoogleGenAIConnectionOptions { /** Google Cloud project id — the VERTEX door. Env fallback (read by the SDK): * `GOOGLE_CLOUD_PROJECT`. */ readonly project?: string; /** Vertex location, e.g. `'us-central1'` or `'global'` (the SDK's default). * Env fallback: `GOOGLE_CLOUD_LOCATION`. */ readonly location?: string; /** * API key — the GEMINI API door. Env fallbacks: `GEMINI_API_KEY`, then * `GOOGLE_API_KEY`. * * A FUNCTION here is re-read before every request, so a short-lived * credential can refresh without rebuilding the provider. See the file * header for what "before every request" does and does not cover. */ readonly apiKey?: GoogleApiKeySource; /** Force the door instead of inferring it: `true` = Vertex, `false` = the * Gemini API. */ readonly vertexai?: boolean; /** Pin the API version. Unset means the SDK's default — `v1beta1` on Vertex, * `v1beta` on the Gemini API. */ readonly apiVersion?: string; /** `GoogleAuthOptions` for the Vertex door — a key file, scopes, or an * `AuthClient` you built (workload identity federation, impersonation). */ readonly googleAuthOptions?: Record; } /** * WHICH SERVICE a set of options addresses. * * The two doors are not two factories, so nothing about them is visible in a * call site's shape — and they do not accept the same models. A live field * trial found the documented default (`gemini-2.5-flash`) working on Vertex * and refused with HTTP 404 on the key door: *"This model models/gemini-2.5-flash * is no longer available to new users"* (FINDINGS "Failure 1"). One silent * default for two services is untenable, so the door has to be a value the * adapter can branch on. */ export type GoogleDoor = 'vertex' | 'gemini-api'; /** * Which door these options open. * * The order mirrors `resolveGoogleGenAIClient` exactly, because a door that * disagreed with the client that gets built is worse than no door at all. * * @param readEnv `false` when the caller injected a `_client`. A double talks * to no service, so the ambient `GEMINI_API_KEY` of whoever is running * the tests describes a door that will never be dialled — reading it * would make an offline suite depend on a developer's shell. */ export declare function resolveGoogleDoor(options: GoogleGenAIConnectionOptions, readEnv?: boolean): GoogleDoor; /** * Build (or accept) a `GoogleGenAI` client. * * `injected` is the `_client` test seam: a structural double exposing only the * namespace members the calling adapter uses. When it is present the SDK is * never required, no environment is read and no refusal fires — a test drives * the adapter's translation with no Google anywhere near it. * * @param factory the public name to put in refusals — `'gemini'`, * `'geminiEmbedder'`. A message that names the wrong factory * sends the reader to the wrong line of their own code. * @param apiKey the key STRING for this build. Callers holding a callback * resolve it first (see {@link createGoogleGenAIClientResolver}); * a function reaching the SDK's `apiKey` field would be sent * as `[object Function]` and fail as an auth error. * @throws when neither a project nor an API key is resolvable; when * `vertexai: true` is asked for without a project; and when * `@google/genai` is not installed. */ export declare function resolveGoogleGenAIClient(options: GoogleGenAIConnectionOptions, factory: string, injected?: T, apiKey?: string): T; /** * One client, and the key it was built with. * * The key travels BESIDE the client because the adapter that made the call is * the thing that redacts it out of a vendor error, and under a callback the * key in force is not the one the options were constructed with. A redactor * given the wrong string is a redactor that does nothing, silently. */ export interface GoogleGenAIClientLease { readonly client: T; /** The key string this client is authenticating with, when there is one. */ readonly apiKey?: string; } /** Ask for the client to use for THIS request. */ export type GoogleGenAIClientResolver = () => Promise>; /** * The client seam every Google adapter calls through — one per request. * * Three paths, and only the third one is new: * * 1. **`_client` injected** — the double, always, no SDK, no environment. * Any `apiKey` still rides along on the lease so redaction tests can * prove a secret never reaches an error message. * 2. **`apiKey` a string (or absent)** — the client is built ONCE. With * `eager`, it is built at factory time, so a missing peer dependency and * an unanswerable door are still refused where the consumer typed the * call rather than on their first request. * 3. **`apiKey` a callback** — called before every request. The client is * rebuilt only when the answer CHANGED, so a cached token costs one * function call. Construction cannot be eager here: calling a consumer's * credential provider from a factory would fetch a token nobody asked * for yet, and possibly before their own setup ran. * * @param eager build now (path 2 only), preserving construction-time refusals. */ export declare function createGoogleGenAIClientResolver(options: GoogleGenAIConnectionOptions, factory: string, injected?: T, eager?: boolean): GoogleGenAIClientResolver;