Files
pi_harness/packages/ai/test/google-vertex-api-key-resolution.test.ts
Mario Zechner 8a0903ebf2 feat(ai): compat entrypoint, core-only root barrel (phase 5)
The root barrel is now core-only and side-effect free: types,
createModels/createProvider, auth substrate, lazyStream/lazyApi, faux,
utils. Generated catalogs, api-registry, env-api-keys, images, global
stream functions, and per-API lazy wrappers leave the root.

New @earendil-works/pi-ai/compat preserves the old surface verbatim as
a strict superset of the root: api-dispatch stream/complete with env
key injection, the builtin registration side effect (skip-if-present so
it cannot clobber earlier overrides), deprecated getModel/getModels/
getProviders aliases of the new getBuiltin* reads in providers/all,
lazy api wrappers + setBedrockProviderModule, and image generation.
Compat dies with the coding-agent ModelManager migration.

Packaging: exports map gains ./compat, ./providers/*, ./api/*;
sideEffects array lists only the effectful modules.

Old-global imports across agent/coding-agent/examples and pi-ai tests
switch to /compat (path-only; compat is a superset). The coding-agent
extension loader resolves the pi-ai ROOT specifier to compat, so
existing user extensions using the old global API keep working at
runtime until compat is removed. vitest configs alias /compat to src;
browser smoke imports old globals from /compat.
2026-06-10 21:17:12 +02:00

224 lines
6.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const googleGenAiMock = vi.hoisted(() => ({
constructorCalls: [] as Array<Record<string, unknown>>,
}));
vi.mock("@google/genai", () => {
class GoogleGenAI {
models = {
generateContentStream: async function* () {
yield {
responseId: "vertex-response-id",
candidates: [
{
content: { parts: [{ text: "ok" }] },
finishReason: "STOP",
},
],
usageMetadata: {
promptTokenCount: 1,
candidatesTokenCount: 1,
totalTokenCount: 2,
},
};
},
};
constructor(config: Record<string, unknown>) {
googleGenAiMock.constructorCalls.push(config);
}
}
return {
GoogleGenAI,
ResourceScope: {
COLLECTION: "COLLECTION",
},
ThinkingLevel: {
THINKING_LEVEL_UNSPECIFIED: "THINKING_LEVEL_UNSPECIFIED",
MINIMAL: "MINIMAL",
LOW: "LOW",
MEDIUM: "MEDIUM",
HIGH: "HIGH",
},
};
});
import { stream as streamGoogleVertex } from "../src/api/google-vertex.ts";
import { getModel } from "../src/compat.ts";
import type { Context, Model } from "../src/types.ts";
const model = getModel("google-vertex", "gemini-3-flash-preview");
const context: Context = {
messages: [{ role: "user", content: "hello", timestamp: Date.now() }],
};
const originalGoogleCloudApiKey = process.env.GOOGLE_CLOUD_API_KEY;
beforeEach(() => {
googleGenAiMock.constructorCalls.length = 0;
delete process.env.GOOGLE_CLOUD_API_KEY;
});
afterEach(() => {
if (originalGoogleCloudApiKey === undefined) {
delete process.env.GOOGLE_CLOUD_API_KEY;
} else {
process.env.GOOGLE_CLOUD_API_KEY = originalGoogleCloudApiKey;
}
});
describe("google-vertex api key resolution", () => {
it("falls back to ADC when options.apiKey is a placeholder marker", async () => {
const stream = streamGoogleVertex(model, context, {
apiKey: "<authenticated>",
project: "test-project",
location: "us-central1",
});
await stream.result();
expect(googleGenAiMock.constructorCalls).toHaveLength(1);
expect(googleGenAiMock.constructorCalls[0]).toMatchObject({
vertexai: true,
project: "test-project",
location: "us-central1",
apiVersion: "v1",
});
expect(googleGenAiMock.constructorCalls[0]).not.toHaveProperty("apiKey");
});
it("falls back to ADC when options.apiKey is the gcp-vertex-credentials marker", async () => {
const stream = streamGoogleVertex(model, context, {
apiKey: "gcp-vertex-credentials",
project: "test-project",
location: "us-central1",
});
await stream.result();
expect(googleGenAiMock.constructorCalls).toHaveLength(1);
expect(googleGenAiMock.constructorCalls[0]).toMatchObject({
vertexai: true,
project: "test-project",
location: "us-central1",
apiVersion: "v1",
});
expect(googleGenAiMock.constructorCalls[0]).not.toHaveProperty("apiKey");
});
it("falls back to ADC when GOOGLE_CLOUD_API_KEY is a placeholder marker", async () => {
process.env.GOOGLE_CLOUD_API_KEY = "<authenticated>";
const stream = streamGoogleVertex(model, context, {
project: "test-project",
location: "us-central1",
});
await stream.result();
expect(googleGenAiMock.constructorCalls).toHaveLength(1);
expect(googleGenAiMock.constructorCalls[0]).toMatchObject({
vertexai: true,
project: "test-project",
location: "us-central1",
apiVersion: "v1",
});
expect(googleGenAiMock.constructorCalls[0]).not.toHaveProperty("apiKey");
});
it("still uses the API key client for real API keys", async () => {
const stream = streamGoogleVertex(model, context, {
apiKey: "AIzaSyExampleRealisticLookingApiKey123456",
});
await stream.result();
expect(googleGenAiMock.constructorCalls).toHaveLength(1);
expect(googleGenAiMock.constructorCalls[0]).toMatchObject({
vertexai: true,
apiKey: "AIzaSyExampleRealisticLookingApiKey123456",
apiVersion: "v1",
});
expect(googleGenAiMock.constructorCalls[0]).not.toHaveProperty("project");
expect(googleGenAiMock.constructorCalls[0]).not.toHaveProperty("location");
});
it("does not forward generated Vertex base URL placeholders", async () => {
const stream = streamGoogleVertex(model, context, {
project: "test-project",
location: "us-central1",
});
await stream.result();
expect(googleGenAiMock.constructorCalls).toHaveLength(1);
expect(googleGenAiMock.constructorCalls[0]?.httpOptions).toBeUndefined();
});
it("forwards custom baseUrl to the ADC client", async () => {
const customModel: Model<"google-vertex"> = { ...model, baseUrl: "https://proxy.example.com" };
const stream = streamGoogleVertex(customModel, context, {
project: "test-project",
location: "us-central1",
});
await stream.result();
expect(googleGenAiMock.constructorCalls).toHaveLength(1);
expect(googleGenAiMock.constructorCalls[0]).toMatchObject({
vertexai: true,
project: "test-project",
location: "us-central1",
apiVersion: "v1",
httpOptions: {
baseUrl: "https://proxy.example.com",
baseUrlResourceScope: "COLLECTION",
},
});
});
it("forwards custom baseUrl to the API key client", async () => {
const customModel: Model<"google-vertex"> = { ...model, baseUrl: "https://proxy.example.com" };
const stream = streamGoogleVertex(customModel, context, {
apiKey: "AIzaSyExampleRealisticLookingApiKey123456",
});
await stream.result();
expect(googleGenAiMock.constructorCalls).toHaveLength(1);
expect(googleGenAiMock.constructorCalls[0]).toMatchObject({
vertexai: true,
apiKey: "AIzaSyExampleRealisticLookingApiKey123456",
apiVersion: "v1",
httpOptions: {
baseUrl: "https://proxy.example.com",
baseUrlResourceScope: "COLLECTION",
},
});
});
it("does not append apiVersion when custom baseUrl already includes one", async () => {
const customModel: Model<"google-vertex"> = {
...model,
baseUrl: "https://proxy.example.com/v1/projects/test-project/locations/global",
};
const stream = streamGoogleVertex(customModel, context, {
project: "test-project",
location: "us-central1",
});
await stream.result();
expect(googleGenAiMock.constructorCalls).toHaveLength(1);
expect(googleGenAiMock.constructorCalls[0]).toMatchObject({
httpOptions: {
baseUrl: "https://proxy.example.com/v1/projects/test-project/locations/global",
baseUrlResourceScope: "COLLECTION",
apiVersion: "",
},
});
});
});