Add Vertex AI provider with ADC support

- Implement google-vertex provider in packages/ai
- Support ADC (Application Default Credentials) via @google/generative-ai
- Add Gemini model catalog for Vertex AI
- Update packages/coding-agent to handle google-vertex provider
This commit is contained in:
Anton Kuzmenko 2025-12-23 23:03:19 -08:00 committed by Mario Zechner
parent d747ec6e23
commit 214e7dae15
11 changed files with 788 additions and 4 deletions

View file

@ -6,6 +6,7 @@ import {
type GoogleThinkingLevel,
streamGoogleGeminiCli,
} from "./providers/google-gemini-cli.js";
import { type GoogleVertexOptions, streamGoogleVertex } from "./providers/google-vertex.js";
import { type OpenAICompletionsOptions, streamOpenAICompletions } from "./providers/openai-completions.js";
import { type OpenAIResponsesOptions, streamOpenAIResponses } from "./providers/openai-responses.js";
import type {
@ -38,6 +39,14 @@ export function getEnvApiKey(provider: any): string | undefined {
return process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
}
// Vertex AI doesn't use API keys.
// It relies on Google Cloud auth: `gcloud auth application-default login`.
// @google/genai library picks up and manages the auth automatically.
// Return a dummy value to maintain consistency.
if (provider === "google-vertex") {
return "vertex-ai-authenticated";
}
const envMap: Record<string, string> = {
openai: "OPENAI_API_KEY",
google: "GEMINI_API_KEY",
@ -85,6 +94,9 @@ export function stream<TApi extends Api>(
providerOptions as GoogleGeminiCliOptions,
);
case "google-vertex":
return streamGoogleVertex(model as Model<"google-vertex">, context, providerOptions as GoogleVertexOptions);
default: {
// This should never be reached if all Api cases are handled
const _exhaustive: never = api;
@ -239,6 +251,44 @@ function mapOptionsForApi<TApi extends Api>(
} satisfies GoogleGeminiCliOptions;
}
case "google-vertex": {
// Explicitly disable thinking when reasoning is not specified
if (!options?.reasoning) {
return { ...base, thinking: { enabled: false } } satisfies GoogleVertexOptions;
}
const vertexModel = model as Model<"google-vertex">;
const effort = clampReasoning(options.reasoning)!;
if (isGemini3ProModel(vertexModel as unknown as Model<"google-generative-ai">)) {
return {
...base,
thinking: {
enabled: true,
level: getGemini3ThinkingLevel(effort, vertexModel as unknown as Model<"google-generative-ai">),
},
} satisfies GoogleVertexOptions;
}
if (isGemini3FlashModel(vertexModel as unknown as Model<"google-generative-ai">)) {
return {
...base,
thinking: {
enabled: true,
level: getGemini3ThinkingLevel(effort, vertexModel as unknown as Model<"google-generative-ai">),
},
} satisfies GoogleVertexOptions;
}
return {
...base,
thinking: {
enabled: true,
budgetTokens: getGoogleBudget(vertexModel as unknown as Model<"google-generative-ai">, effort),
},
} satisfies GoogleVertexOptions;
}
default: {
// Exhaustiveness check
const _exhaustive: never = model.api;