kestrel.markets
Version:
A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.
201 lines (183 loc) • 11.2 kB
text/typescript
/**
* # session/harness/vertex-maas — Vertex Model Garden MaaS plumbing (ADC bearer + the region trap) (kestrel-k8he)
*
* Vertex AI Model Garden serves the open frontier weights (DeepSeek V3.2, Qwen3-Next-80B, Kimi K2
* Thinking) **as-a-service on GCP CREDITS**, over an OpenAI-compatible chat-completions endpoint.
* Live probe (wf_e58255fd-e01, 2026-07-18) verified all three MaaS slugs serve 200 on
*
* `https://aiplatform.googleapis.com/v1beta1/projects/<project>/locations/global/endpoints/openapi/chat/completions`
*
* with an **ADC bearer** (`gcloud auth application-default print-access-token`) — NOT an API key.
* This module owns everything about that path that does not touch a provider package (so it stays
* importable by cost-planning / test code): the base-URL builder, the fail-closed project + location
* resolution, and the injectable gcloud-ADC token source. The wire-touching client construction
* (`createOpenAICompatible` + the bearer-attaching fetch) lives in `./ai-sdk-client.ts`, the ONE
* provider-bearing module.
*
* ## THE REGION TRAP (pin location=global, fail LOUDLY on anything else)
* The same live probe showed `us-central1` rejects EVERY MaaS slug ("Model … is not available in
* region"); only `location=global` serves them. A harness configured with a region — an
* `opts.region`, a `GOOGLE_VERTEX_LOCATION`/`GOOGLE_CLOUD_LOCATION` in the shell (both common gcloud
* conventions) — must therefore FAIL with the clear region error at construction, never silently
* "helpfully" rewrite the location to global: a silent rewrite would hide that the caller's config
* disagrees with the wire, and the next person to copy that config onto a raw curl would get the
* opaque vendor 404 with no breadcrumb. {@link resolveVertexMaasLocation} is that guard.
*
* ## DEAD ADC: surface the login, never silently skip (the gcloud auto-reauth doctrine)
* An ADC bearer expires (~1h) and dies entirely when the grant is revoked. When the refresh
* subprocess fails, {@link gcloudAdcTokenSource} throws {@link AdcAuthError} carrying the EXACT
* reauth command (`gcloud auth application-default login`) — the documented doctrine is that an
* agent RUNS that login (it opens the owner's browser) and waits, and NEVER quietly skips the
* Vertex lane. What this code must never do is swallow the failure and let a runner tally the seat
* as "no result" as if the lane did not exist.
*/
import { execFile } from "node:child_process";
/** The ONLY location Vertex Model Garden MaaS serves from (probe wf_e58255fd-e01: us-central1
* rejects all MaaS slugs). Pinned; a configured non-global location throws — see the module doc. */
export const VERTEX_MAAS_LOCATION = "global";
/** The provider `name` given to `createOpenAICompatible` — also the `providerOptions` key an
* openai-compatible model reads passthrough options from (same convention as `fireworks`). */
export const VERTEX_MAAS_PROVIDER_NAME = "vertex-maas";
/** The env names a GCP project id may arrive under, in precedence order. `GOOGLE_VERTEX_PROJECT` is
* the `@ai-sdk/google-vertex` convention; the other two are gcloud's own. */
export const VERTEX_MAAS_PROJECT_ENV_VARS = ["GOOGLE_VERTEX_PROJECT", "GOOGLE_CLOUD_PROJECT", "GCLOUD_PROJECT"] as const;
/** The env names a location/region may leak in from (both gcloud conventions). Read ONLY to fail
* loudly on the region trap — never to actually route anywhere but {@link VERTEX_MAAS_LOCATION}. */
export const VERTEX_MAAS_LOCATION_ENV_VARS = ["GOOGLE_VERTEX_LOCATION", "GOOGLE_CLOUD_LOCATION"] as const;
/** The GCP project id from the environment (first present, non-trivial value wins), or `undefined`.
* A project id is an ADDRESS, not a secret — but it is account-specific, so it is never hardcoded. */
export function vertexMaasProjectFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {
for (const name of VERTEX_MAAS_PROJECT_ENV_VARS) {
const v = env[name];
if (typeof v === "string" && v.trim() !== "") return v.trim();
}
return undefined;
}
/** Fail-closed project resolution: the env project, or a THROW naming the env vars to set. A blank
* project would 404 opaquely on the wire (the URL embeds it); this error says what to fix instead. */
export function requireVertexMaasProject(env: NodeJS.ProcessEnv = process.env): string {
const project = vertexMaasProjectFromEnv(env);
if (project === undefined) {
throw new Error(
`vertex-maas: no GCP project id — set one of ${VERTEX_MAAS_PROJECT_ENV_VARS.join(" / ")} ` +
`(the MaaS endpoint URL embeds the project; refusing to construct a client that can only 404).`,
);
}
return project;
}
/** Raised when a vertex-maas client is configured with a non-global location — the REGION TRAP made
* loud. Fail-closed: the caller's config disagrees with the only location the wire serves, so the
* client refuses to construct rather than silently rewriting the location (see the module doc). */
export class VertexMaasRegionError extends Error {
constructor(configured: string, sourceOfTruth: string) {
super(
`vertex-maas: location "${configured}" (from ${sourceOfTruth}) is not available for Vertex Model Garden MaaS — ` +
`regional endpoints (us-central1 included) reject ALL MaaS model slugs with "not available in region" ` +
`(live probe wf_e58255fd-e01, 2026-07-18). The ONLY serving location is "${VERTEX_MAAS_LOCATION}". ` +
`Drop the region override (opts.region / ${VERTEX_MAAS_LOCATION_ENV_VARS.join(" / ")}) or set it to "${VERTEX_MAAS_LOCATION}" — ` +
`refusing to silently rewrite your configured location.`,
);
this.name = "VertexMaasRegionError";
}
}
/**
* Resolve the MaaS serving location: always {@link VERTEX_MAAS_LOCATION}, and a THROW
* ({@link VertexMaasRegionError}) when the caller CONFIGURED anything else — an explicit
* `configured` (the client's `region` option) first, else a location leaked in from the gcloud env
* conventions. A `configured`/env value of exactly `"global"` is accepted (it agrees with the pin).
*/
export function resolveVertexMaasLocation(configured?: string, env: NodeJS.ProcessEnv = process.env): string {
if (configured !== undefined && configured.trim() !== "" && configured.trim() !== VERTEX_MAAS_LOCATION) {
throw new VertexMaasRegionError(configured.trim(), "the client's region option");
}
for (const name of VERTEX_MAAS_LOCATION_ENV_VARS) {
const v = env[name];
if (typeof v === "string" && v.trim() !== "" && v.trim() !== VERTEX_MAAS_LOCATION) {
throw new VertexMaasRegionError(v.trim(), name);
}
}
return VERTEX_MAAS_LOCATION;
}
/** The OpenAI-compatible base URL for a project's MaaS endpoint. `createOpenAICompatible` appends
* `/chat/completions` — this is everything before it (verified live, probe wf_e58255fd-e01). */
export function vertexMaasBaseUrl(project: string, location: string = VERTEX_MAAS_LOCATION): string {
return `https://aiplatform.googleapis.com/v1beta1/projects/${project}/locations/${location}/endpoints/openapi`;
}
/** One ADC-refresh subprocess result — the injectable seam, same shape discipline as the codex-cli
* lane's `CodexExecFn` (tests inject a stand-in; the real one spawns gcloud). */
export interface AdcExecResult {
readonly stdout: string;
readonly stderr: string;
readonly exitCode: number;
}
/** The injectable subprocess seam: run `cmd args…` and hand back its streams + exit code. */
export type AdcExecFn = (cmd: string, args: readonly string[]) => Promise<AdcExecResult>;
/** Raised when the ADC bearer cannot be refreshed — dead/expired Application Default Credentials.
* The message carries the exact reauth command; per the gcloud auto-reauth doctrine the agent runs
* it (browser login) and waits — the Vertex lane is NEVER silently skipped. */
export class AdcAuthError extends Error {
constructor(detail: string) {
super(
`vertex-maas: gcloud ADC token refresh failed — ${detail}. ` +
`Application Default Credentials are dead or absent. Run \`gcloud auth application-default login\` ` +
`(opens the browser login; wait for it to complete), then retry. Per the gcloud auto-reauth doctrine ` +
`this lane must SURFACE the login and wait — never silently skip the Vertex seat.`,
);
this.name = "AdcAuthError";
}
}
/** How long a fetched ADC bearer is reused before re-running gcloud (tokens live ~60 min; 45 keeps a
* comfortable margin so a long fan-out never sends a just-expired bearer). */
export const ADC_TOKEN_TTL_MS = 45 * 60 * 1000;
/** A refreshable ADC bearer-token source. `token()` returns a live bearer (cached within
* {@link ADC_TOKEN_TTL_MS}); `issued()` lists every token value this source has ever handed out —
* the belt-and-suspenders feed into `scrubSecrets`, so an ADC bearer (which lives in NO env var and
* would escape the `CREDENTIAL_ENV_VARS` scrub) can still never reach a wire-evidence artifact. */
export interface AdcTokenSource {
token(): Promise<string>;
issued(): readonly string[];
}
/** The REAL subprocess seam: spawn `gcloud auth application-default print-access-token`. */
const realAdcExec: AdcExecFn = (cmd, args) =>
new Promise((resolve) => {
execFile(cmd, [...args], { timeout: 30_000 }, (error, stdout, stderr) => {
const code = (error as NodeJS.ErrnoException | null)?.code;
const exitCode = error === null ? 0 : typeof code === "number" ? code : 1;
resolve({ stdout: String(stdout), stderr: String(stderr), exitCode });
});
});
/**
* The gcloud-ADC {@link AdcTokenSource}: refresh via `gcloud auth application-default
* print-access-token`, cache for {@link ADC_TOKEN_TTL_MS}, and FAIL LOUDLY ({@link AdcAuthError},
* naming the reauth command) on a dead ADC — never a silent skip. `exec` is the test seam (same
* pattern as the codex-cli lane); NEVER set on a production client.
*/
export function gcloudAdcTokenSource(opts: { readonly exec?: AdcExecFn; readonly now?: () => number } = {}): AdcTokenSource {
const exec = opts.exec ?? realAdcExec;
const now = opts.now ?? Date.now;
const handedOut: string[] = [];
let cached: { readonly token: string; readonly at: number } | undefined;
return {
async token(): Promise<string> {
if (cached !== undefined && now() - cached.at < ADC_TOKEN_TTL_MS) return cached.token;
let r: AdcExecResult;
try {
r = await exec("gcloud", ["auth", "application-default", "print-access-token"]);
} catch (e) {
throw new AdcAuthError(`could not spawn gcloud (${e instanceof Error ? e.message : String(e)})`);
}
const token = r.stdout.trim();
if (r.exitCode !== 0 || token === "") {
throw new AdcAuthError(
`exit ${r.exitCode}${r.stderr.trim() !== "" ? `: ${r.stderr.trim()}` : token === "" ? ": empty token" : ""}`,
);
}
cached = { token, at: now() };
if (!handedOut.includes(token)) handedOut.push(token);
return token;
},
issued(): readonly string[] {
return handedOut;
},
};
}