fix(ai): ignore placeholder vertex api keys closes #2335
This commit is contained in:
@@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
- Added `gpt-5.4-mini` model support for the `openai-codex` provider with Codex pricing metadata and unit coverage.
|
- Added `gpt-5.4-mini` model support for the `openai-codex` provider with Codex pricing metadata and unit coverage.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `google-vertex` API key resolution to ignore placeholder auth markers like `<authenticated>` and fall back to ADC instead of sending them as literal API keys ([#2335](https://github.com/badlogic/pi-mono/issues/2335))
|
||||||
|
|
||||||
## [0.60.0] - 2026-03-18
|
## [0.60.0] - 2026-03-18
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -368,7 +368,15 @@ function createClientWithApiKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveApiKey(options?: GoogleVertexOptions): string | undefined {
|
function resolveApiKey(options?: GoogleVertexOptions): string | undefined {
|
||||||
return options?.apiKey || process.env.GOOGLE_CLOUD_API_KEY;
|
const apiKey = options?.apiKey?.trim() || process.env.GOOGLE_CLOUD_API_KEY?.trim();
|
||||||
|
if (!apiKey || isPlaceholderApiKey(apiKey)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlaceholderApiKey(apiKey: string): boolean {
|
||||||
|
return /^<[^>]+>$/.test(apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveProject(options?: GoogleVertexOptions): string {
|
function resolveProject(options?: GoogleVertexOptions): string {
|
||||||
|
|||||||
125
packages/ai/test/google-vertex-api-key-resolution.test.ts
Normal file
125
packages/ai/test/google-vertex-api-key-resolution.test.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
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,
|
||||||
|
ThinkingLevel: {
|
||||||
|
THINKING_LEVEL_UNSPECIFIED: "THINKING_LEVEL_UNSPECIFIED",
|
||||||
|
MINIMAL: "MINIMAL",
|
||||||
|
LOW: "LOW",
|
||||||
|
MEDIUM: "MEDIUM",
|
||||||
|
HIGH: "HIGH",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { getModel } from "../src/models.js";
|
||||||
|
import { streamGoogleVertex } from "../src/providers/google-vertex.js";
|
||||||
|
import type { Context } from "../src/types.js";
|
||||||
|
|
||||||
|
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 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user