/** * adapters/google/aiPlatform — the one place this package builds a Vertex AI * REST client, names a Vertex resource, waits on a Vertex operation, or * describes a Vertex failure. * * Three adapters sit on top of it — `agentEngineSessions`, `memoryBankStore` * and (for its auth half only) `googleIdentity` — and none of them repeats any * of the four things above. That is the whole reason this file exists: the AWS * column learned, expensively, that a shim duplicated across three adapters is * three places for the same wire fact to go stale, and only one of them gets * fixed. * * ── Which client, and why THIS one ────────────────────────────────────────── * `@googleapis/aiplatform` — the **split, per-API** discovery package (31.0.0, * 27 MB) rather than the `googleapis` mega-package (174.0.1, 209 MB) that * carries every Google API at once for the same generated code. Both were * installed and measured before this was written. * * The gax/proto client (`@google-cloud/aiplatform`, 78 MB) was rejected on a * fact rather than on size: its Memory Bank surface is **v1beta1 only**, while * the REST **v1** surface is complete — `memories.purge` and `memories.rollback` * exist at v1 and do not exist at v1beta1. One client, at the version that has * everything, beats two clients at two versions. * * `@google-cloud/vertexai` is past its own announced removal date and is not * loaded anywhere in this package, ever. * * ── The endpoint fact that is not in anybody's design doc ─────────────────── * The generated client defaults to `https://aiplatform.googleapis.com/` — the * GLOBAL host. Reasoning engines, their sessions and their memories are * **regional** resources. An adapter that accepts the default calls the wrong * host and gets a 404 that reads exactly like "your resource does not exist". * So {@link buildAiPlatformClient} always sets a regional `rootUrl`, derived * from the same `location` that composes the resource name, and the surface pin * asserts the default really is the global one so this comment cannot go stale * quietly. * * ── Secrets ───────────────────────────────────────────────────────────────── * {@link googleSdkFailure} is the `sdkFailure` law from the AWS identity * adapter, re-aimed at a Gaxios error: the SDK's own message never comes * through. It is not that a Vertex error always contains a secret — it is that * a REST client echoes the request into failure text, our requests carry * conversation state and access tokens, and an error message here reaches the * model as a tool result AND rides the event stream to every sink attached. * One echo publishes it to both at once. */ /** * One REST call's answer. The discovery clients wrap every response as * `{ data }`, and that wrapper is part of the contract an injected `_client` * has to honour. */ export interface RestResponse { readonly data: T; } /** A long-running operation, as these adapters read it. */ export interface LongRunningOperation { readonly name?: string | null; readonly done?: boolean | null; readonly error?: { readonly code?: number | null; readonly message?: string | null; } | null; readonly response?: Record | null; } /** The `operations` sub-resource, which both collections carry identically. */ export interface OperationsApi { /** * Block server-side until the operation finishes or `timeout` elapses, then * answer whatever state it is in. * * `wait` rather than a `get` poll loop: it is the operation the service * publishes for exactly this, and it turns "poll every 200 ms for ten * seconds" into one or two round trips. It may still answer un-done, so * {@link awaitOperation} loops it against a deadline — a server-side wait is * not a promise that the work is finished, and treating it as one is how an * adapter reads a resource that is not there yet. */ wait(params: { name: string; timeout?: string; }): Promise | undefined>; } /** A Vertex `Session`, in the fields these adapters read or write. */ export interface VertexSession { readonly name?: string | null; readonly userId?: string | null; readonly sessionState?: Record | null; readonly createTime?: string | null; readonly updateTime?: string | null; readonly expireTime?: string | null; readonly ttl?: string | null; } /** * One appended session event — **the only way session STATE may be changed.** * * A live field trial established this against the real service (2026-08-14): * `sessions.patch` with `updateMask: 'sessionState'` is refused with HTTP 400 * and the service's own words, recovered through a raw diagnostic that carried * no conversation state: * * "Can't update the session state for session …, you can only update it by * appending an event." * * The same trial then sent `appendEvent` with `actions.stateDelta` and the * following `GET` returned the new state. So the fields below are not the whole * `SessionEvent` message — they are the four this column writes: the three the * message marks Required (`author`, `invocationId`, `timestamp`) plus the * `actions.stateDelta` that carries the change. */ export interface VertexSessionEvent { /** Required by the message: who sent it. */ readonly author?: string | null; /** Required by the message: which invocation it belongs to. */ readonly invocationId?: string | null; /** Required by the message: RFC 3339, client-side. */ readonly timestamp?: string | null; /** What the event DOES. `stateDelta` is the half this column uses. */ readonly actions?: { readonly stateDelta?: Record | null; } | null; } /** The `sessions` collection. Every method here is pinned. */ export interface SessionsApi { create(params: { parent: string; sessionId?: string; requestBody: VertexSession; }): Promise | undefined>; get(params: { name: string; }): Promise | undefined>; /** * Append one event — and, through `actions.stateDelta`, the only supported * way to change `sessionState` after creation. Synchronous: it answers a * response, not a long-running operation. */ appendEvent(params: { name: string; requestBody: VertexSessionEvent; }): Promise> | undefined>; /** * **Not for `sessionState`.** See {@link VertexSessionEvent}: the live * service refuses a state patch by name. Pinned because the method exists and * the surface pin asserts what this column can reach; no adapter here sends a * `sessionState` mask through it. */ patch(params: { name: string; updateMask?: string; requestBody: VertexSession; }): Promise | undefined>; delete(params: { name: string; }): Promise | undefined>; list(params: { parent: string; filter?: string; orderBy?: string; pageSize?: number; pageToken?: string; }): Promise | undefined>; readonly operations: OperationsApi; } /** One typed metadata scalar. Memory Bank's metadata map is NOT free JSON. */ export interface VertexMemoryMetadataValue { readonly stringValue?: string | null; readonly doubleValue?: number | null; readonly boolValue?: boolean | null; readonly timestampValue?: string | null; } /** A Vertex `Memory`, in the fields these adapters read or write. */ export interface VertexMemory { readonly name?: string | null; readonly fact?: string | null; readonly scope?: Record | null; readonly metadata?: Record | null; readonly createTime?: string | null; readonly updateTime?: string | null; readonly expireTime?: string | null; readonly ttl?: string | null; } /** One row of a retrieval answer — and the whole ranking trap in two fields. */ export interface VertexRetrievedMemory { /** * **A DISTANCE. Smaller is closer.** The SDK's own words for this field are * "the distance between the query and the retrieved Memory. Smaller values * indicate more similar memories." * * The port's `ScoredEntry.score` is a cosine similarity, where HIGHER is * closer. These two facts are the silent-inversion trap this column was * warned about, and the type says so here so that nothing downstream can * copy it into a `score` field by looking only at its shape. * * Only set when similarity search was used; a simple retrieval has no * ranking at all and reports none. */ readonly distance?: number | null; readonly memory?: VertexMemory; } /** The `memories` collection. Every method here is pinned. */ export interface MemoriesApi { create(params: { parent: string; memoryId?: string; requestBody: VertexMemory; }): Promise | undefined>; get(params: { name: string; }): Promise | undefined>; patch(params: { name: string; updateMask?: string; requestBody: VertexMemory; }): Promise | undefined>; delete(params: { name: string; }): Promise | undefined>; list(params: { parent: string; filter?: string; pageSize?: number; pageToken?: string; }): Promise | undefined>; retrieve(params: { parent: string; requestBody: { scope: Record; similaritySearchParams?: { searchQuery: string; topK?: number; }; simpleRetrievalParams?: { pageSize?: number; pageToken?: string; }; }; }): Promise | undefined>; readonly operations: OperationsApi; } /** * The shape both adapters call, and the shape an injected `_client` must have. * * It is the SDK's own nesting rather than a flattened convenience surface, on * purpose: the surface pin walks these exact property paths against the really * installed package, and a flattened shim would move the names being checked * out of the file that does the calling. */ export interface AiPlatformClientLike { readonly projects: { readonly locations: { readonly reasoningEngines: { readonly sessions: SessionsApi; readonly memories: MemoriesApi; }; }; }; } /** The slice of `@googleapis/aiplatform` this module loads. */ export interface AiPlatformSdkModule { readonly aiplatform?: (options: { version: string; rootUrl?: string; auth?: unknown; }) => AiPlatformClientLike; readonly auth?: { readonly GoogleAuth?: new (options: unknown) => unknown; }; } /** * The API version every adapter in this column dials, **stated rather than * defaulted**. * * v1, not v1beta1, and that is a decision with evidence behind it: at v1 the * memories collection carries `purge` and `rollback`; at v1beta1 it does not. * The Google column's pin asserts this against the installed package on every * test run, because "which version does this really call" is the question that * made the pin exist. */ export declare const AI_PLATFORM_API_VERSION: "v1"; /** The OAuth scope every Vertex data-plane call needs. */ export declare const CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; /** What every adapter in this column needs to reach one reasoning engine. */ export interface AiPlatformConnection { /** * The Google Cloud project. Read from `GOOGLE_CLOUD_PROJECT` when omitted; * refused by name when neither is available, because a project guessed wrong * is a 404 that reads like a missing resource. */ readonly project?: string; /** * The region, e.g. `'us-central1'`. **Load-bearing twice over:** it composes * the resource name AND selects the regional host. There is no default — a * guess here calls the wrong continent. */ readonly location: string; /** * The reasoning engine these sessions or memories live under: either the * bare id (`'1234567890'`) or a full resource name * (`'projects/p/locations/l/reasoningEngines/1234567890'`), in which case * the project and location are read from it and need not be repeated. * * A reasoning engine resource is REQUIRED even when you deploy no code to * it: sessions and memories are children of it, and there is nowhere else to * put them. Creating one is a control-plane job this library does not do. */ readonly reasoningEngine: string; /** * Credentials. Anything the discovery client accepts — a `GoogleAuth`, an * `OAuth2Client`, an `Impersonated`. Omit and Application Default * Credentials are used with the cloud-platform scope, which is the ordinary * path on Cloud Run, GKE, or a developer machine that has run * `gcloud auth application-default login`. */ readonly auth?: unknown; /** * @internal Test seam — a pre-built client. Skips the SDK load entirely, so * a suite never needs the package installed or a credential configured. */ readonly _client?: AiPlatformClientLike; /** @internal Test seam — the SDK module, to exercise the real construction. */ readonly _sdk?: AiPlatformSdkModule; } /** A resolved engine: the parent resource name, plus the pieces it was built from. */ export interface EngineScope { readonly project: string; readonly location: string; readonly engineId: string; /** `projects/{project}/locations/{location}/reasoningEngines/{engine}` */ readonly parent: string; } /** * Work out which reasoning engine this adapter talks to, from either spelling * of `reasoningEngine`. * * A full resource name wins over `project` / `location` and is CHECKED against * them rather than silently overriding: two spellings of one fact that could * disagree is the same law the builder refuses `agent` beside `agentFactory` * under, and a name that says `us-central1` under a config that says * `europe-west4` is a bug somebody should be told about, not arbitrated. */ export declare function resolveEngine(adapter: string, connection: AiPlatformConnection): EngineScope; /** * The regional host for a location. * * See the module header: the client's own default is the global host, and * these resources are regional. This is the fix, in one line, applied at every * construction. */ export declare function regionalRootUrl(location: string): string; /** * Build the REST client, or refuse by name. * * An injected `_client` short-circuits everything below it, which is how every * test in this column runs with no package installed and no credential. */ export declare function buildAiPlatformClient(adapter: string, connection: AiPlatformConnection, scope: EngineScope): AiPlatformClientLike; /** How long {@link awaitOperation} keeps waiting before it refuses, by default. */ export declare const DEFAULT_OPERATION_TIMEOUT_MS = 30000; /** * Wait for a long-running operation to finish, or refuse by name. * * ── Why any of this exists ────────────────────────────────────────────────── * `sessions.create`, `sessions.delete`, `memories.create`, `memories.patch` * and `memories.delete` **all answer with an Operation, not with the resource** * — verified against the installed package's own return types before a line of * either adapter was written. `get`, `list`, `patch`-on-a-session and * `retrieve` are the synchronous ones. * * That asymmetry is the trap: a `put` that returns as soon as the service * accepts the request, followed by a `get`, is a race that passes on a warm * day and fails under load — and fails by answering "no data", which is * indistinguishable from the truth. So every write in this column goes through * here and does not return until the service says `done`. * * An operation that finished with an error is raised as one. The operation's * own message is a Google-side description of OUR request, so it is subject to * the same secrecy law as everything else here: the code comes through, the * text does not. */ export declare function awaitOperation(adapter: string, operations: OperationsApi, operation: LongRunningOperation | undefined, what: string, timeoutMs?: number): Promise; /** * The name every refusal from this function carries. * * It exists so a caller's `catch` can tell "the operation went wrong" — which * is already sanitized and already says what to do — apart from "the transport * went wrong", which still needs {@link googleSdkFailure} run over it. Without * the distinction an adapter re-wraps its own refusal and replaces a precise * diagnosis ("did not finish within 30000ms") with a generic one. */ export declare const OPERATION_ERROR_NAME = "GoogleOperationError"; /** * Has this error already been sanitized by this module? Used by every adapter's * `catch` so a refusal is reported once rather than wrapped twice. */ export declare function isSanitizedGoogleError(err: unknown): boolean; /** * Re-raise a failed REST call **without its text** — the `sdkFailure` law from * the AWS identity adapter, re-aimed at a Gaxios error. * * What comes through is the part that is both safe and actionable: which * operation failed and the HTTP status. What does not is the message, because * a REST client echoes the request into its failure text and these requests * carry a whole conversation's state, a user id, and an access token in the * headers. A thrown message here reaches the LLM as a tool result AND rides the * event stream to every sink attached to the agent. * * **The original is deliberately not attached as `cause`** — a cause travels * with the error into every serializer that walks own properties, which would * undo all of this in one `JSON.stringify`. */ export declare function googleSdkFailure(adapter: string, operation: string, err: unknown): Error; /** * The HTTP status of a failed call, wherever this client happened to put it. * * Three places rather than one because the client reports a transport failure, * an API error and a thrown `GaxiosError` slightly differently, and a * classification that only reads one of them silently stops classifying the * day the client is upgraded. */ export declare function httpStatusOf(err: unknown): number | undefined; /** Is this the service's "no such resource"? */ export declare function isNotFound(err: unknown): boolean; /** Is this "a resource by that name already exists"? */ export declare function isAlreadyExists(err: unknown): boolean; /** The longest a resource id may be in both collections this column writes to. */ export declare const MAX_RESOURCE_ID_LENGTH = 63; /** * The id grammar this column really has to satisfy — the INTERSECTION of the * two documented rules, so one function is right for both callers and neither * has to remember which is stricter: `[a-z0-9-]`, first character a letter, * last character a letter or a digit. * * Exported so the surface pin can hold it against the installed package's own * words rather than against this file's memory of them. */ export declare const LEGAL_RESOURCE_ID: RegExp; export declare function safeResourceId(raw: string, max?: number): string; /** * A stable fingerprint of a string, in `[0-9a-z-]`. * * TWO FNV-1a lanes rather than one, because of what this hash is now asked to * carry: it is the only thing keeping two ids apart once the fold has run them * together, and it is half of how `memoryBankStore` addresses one identity's * memories apart from another's. A single 32-bit lane collides at around one * chance in a hundred across ten thousand ids, which is too often for either * job; two lanes cost one more multiply per character and make it negligible. * * It is a fingerprint, not a security boundary — a colliding address is still * refused by the scope check that runs before every read and every overwrite. */ export declare function fingerprint(value: string): string;