/** * googleIdentity — the {@link CredentialProvider} port over Google's own * credential machinery (peer-dep `google-auth-library`). * * import { googleIdentity } from 'agentfootprint/security'; * const credentials = googleIdentity(); * * ── What it is, and what it deliberately is not ───────────────────────────── * This is the **narrow** adapter: it vends *Google* access tokens for *Google* * APIs, from whatever credential the environment already has — Application * Default Credentials on Cloud Run or GKE, a workload-identity federation * config, a service account, optionally impersonating another service account. * That is one job and it is done completely. * * It is **not** a token vault and does not pretend to be one. The other * column's identity adapter can vend a *GitHub* token for a *user* because * that service runs a vault with per-user OAuth grants behind it. Google's * equivalent — the Agent Identity auth manager — is Preview with no Node * surface, so `mode: 'user'` here is **refused by name** rather than quietly * served with a machine token. A machine token returned where a user token was * asked for is the exact silent downgrade the port exists to prevent: the call * succeeds, the data comes back, and it was the agent's access rather than the * person's. See {@link googleIdentity} for the refusal's wording. * * ── The one-hour fact, and where it bites ─────────────────────────────────── * A Google OAuth access token lives about an hour. That is fine wherever the * credential is fetched per use — which is how `ctx.credential` works, so the * ordinary path is unaffected. It bites in exactly one place, and it is worth * naming because it looks like it should work: * * > **The OpenAI-compatible endpoint trap.** Google publishes an * > OpenAI-compatible Gemini endpoint, and `openai({ baseURL, apiKey })` does * > reach it. If you fill that `apiKey` with a token from here, it works for * > an hour and then every call fails with a 401 — because `apiKey` is a * > STRING captured when the provider is constructed, and a long-lived agent * > process outlives it. There is no refresh home in the OpenAI provider's * > options for a credential that expires. Use the native `gemini()` provider, * > which reads ADC through the SDK and refreshes underneath you. * * `expiresAt` is reported on every issued credential so a caller that caches * one can tell. This adapter never caches: {@link CachedGoogleClient} keeps * the *client*, and the client's own refresh logic keeps the token fresh. * * ── Secrets ───────────────────────────────────────────────────────────────── * The `sdkFailure` law, same as every other credential-touching adapter here: * the library's own message never comes through, because auth libraries echo * request detail into failure text and a message thrown from a * `CredentialProvider` reaches the LLM as a tool result AND rides * `agentfootprint.credential.failed` to every sink. What comes through is the * operation that failed and the error's NAME. The original is not attached as * `cause` — a cause travels into every serializer that walks own properties, * which would undo all of it in one `JSON.stringify`. * * Pattern: Adapter (GoF) + lazy peer-dep load — `google-auth-library` is * required the first time `getCredential` runs, or never if you inject one. */ import type { CredentialProvider } from '../../identity/types.js'; /** The scope every Google Cloud data-plane API accepts. */ export declare const CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; /** * The slice of an auth client this adapter calls — a `GoogleAuth`, an * `OAuth2Client` and an `Impersonated` all satisfy it, which is why nothing * here switches on which one it has. */ export interface GoogleAuthClientLike { /** The current access token, refreshed if the library thinks it is stale. */ getAccessToken(): Promise<{ token?: string | null; } | string | null | undefined>; /** Where the library records the expiry it knows about, in unix ms. */ readonly credentials?: { readonly expiry_date?: number | null; }; } /** The slice of a `GoogleAuth` this adapter calls. */ export interface GoogleAuthLike { getClient(): Promise; } /** The slice of `google-auth-library` this adapter loads. */ export interface GoogleAuthSdkModule { readonly GoogleAuth?: new (options: { scopes?: readonly string[]; }) => GoogleAuthLike; readonly Impersonated?: new (options: { sourceClient: GoogleAuthClientLike; targetPrincipal: string; targetScopes: string[]; delegates?: string[]; lifetime?: number; }) => GoogleAuthClientLike; } /** Impersonate another service account for the tokens this provider vends. */ export interface GoogleImpersonation { /** * The service account to act as, e.g. * `'agent-runner@my-project.iam.gserviceaccount.com'`. The credential the * environment already has must hold `roles/iam.serviceAccountTokenCreator` * on it; without that the exchange fails with a 403 whose text this adapter * withholds, so the permission is worth checking before you wonder why. */ readonly targetPrincipal: string; /** A delegation chain, when one account cannot impersonate the target directly. */ readonly delegates?: readonly string[]; /** * How long the impersonated token lives, in seconds. The service's own * ceiling is 3600 (one hour) unless the org policy raises it, and asking for * more than it allows is refused by Google rather than clamped. */ readonly lifetimeSeconds?: number; } /** Options for {@link googleIdentity}. */ export interface GoogleIdentityOptions { /** * The OAuth scopes to request. Default `[cloud-platform]`, which is what * every Google Cloud data-plane API accepts. * * A request's own `scopes` win when it names any — a tool that knows it only * needs read access should say so, and this is where that is honoured. */ readonly scopes?: readonly string[]; /** * Act as another service account. Off by default; see * {@link GoogleImpersonation}. */ readonly impersonate?: GoogleImpersonation; /** * Which downstream services this provider will answer for. * * Unset — the default — it answers for ANY `service`, because the token it * vends is a Google credential and the caller knows better than this adapter * which Google API they are about to call. * * Set it and a request for a service outside the list is refused BY NAME * rather than served. That is the useful setting in a deployment where tools * declare `needs: [{ credential: 'github' }]` alongside Google ones: without * it, this provider would happily hand a Google access token to the tool * that wanted a GitHub one, and the failure would surface as a puzzling 401 * from GitHub rather than as a wiring error here. */ readonly services?: readonly string[]; /** Stable provider id (default `'google-identity'`). */ readonly id?: string; /** * @internal Test seam — a pre-built auth client. Bypasses the SDK entirely, * so the suite runs with no package and no credential. */ readonly _client?: GoogleAuthClientLike; /** @internal Test seam — the SDK module, to exercise the real construction. */ readonly _sdk?: GoogleAuthSdkModule; } /** * Vend Google access tokens from whatever credential this environment has. * * **Status: field-validated for machine identity; refresh bounded.** An * independent trial ran this adapter on live Google Cloud (2026-08-14): * `googleIdentity({ services: ['aiplatform'] })` vended a real bearer * credential from Application Default Credentials, and that credential * authorized a Vertex AI request with HTTP 200. The same run proved the * `bearer` kind, an expiry about 3,599 seconds out, a second vend without * reconstruction, `mode: 'user'` failing CLOSED rather than quietly handing * back machine access, a disallowed service failing closed, and * `JSON.stringify(credential)` yielding `{"kind":"bearer"}` — no token, no * Authorization header. * * **What is NOT proven:** an expiry-triggered refresh. The trial called the * provider twice minutes apart and checked a future expiry; it did not wait an * hour. Google's own auth client is what refreshes (this adapter caches the * CLIENT, never a token — see {@link CachedGoogleClient}), and that refresh was * field-proved on a different door (`gemini({ googleAuthOptions })`). A soak or * a controlled expiring-credential double is still owed before anyone claims * long-duration production proof for THIS provider. * * @throws when `mode: 'user'` is requested — Google's per-user token vault has * no Node surface, and a machine token returned in its place would be a * silent downgrade. * @throws when `services` is configured and the request names another one. * * @example A tool that calls a Google API with the deployment's own identity * const agent = Agent.create({ provider, credentials: googleIdentity() }) * .tool(defineTool({ * name: 'read_sheet', * needs: [{ credential: 'sheets', scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'] }], * execute: async (args, ctx) => * fetch(url, { headers: ctx.credential!.toHeaders() }).then((r) => r.text()), * })) * .build(); * * @example Acting as a dedicated service account * googleIdentity({ * impersonate: { targetPrincipal: 'agent-runner@my-project.iam.gserviceaccount.com' }, * }); */ export declare function googleIdentity(options?: GoogleIdentityOptions): CredentialProvider; //# sourceMappingURL=google.d.ts.map