diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index d76cc902ae6d..ceb9004b7c0b 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -351,9 +351,16 @@ export namespace Provider { }, } }, - "google-vertex": async () => { - const project = Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT") - const location = Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-east5" + "google-vertex": async (provider) => { + const project = + provider.options?.project ?? + Env.get("GOOGLE_CLOUD_PROJECT") ?? + Env.get("GCP_PROJECT") ?? + Env.get("GCLOUD_PROJECT") + + const location = + provider.options?.location ?? Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-central1" + const autoload = Boolean(project) if (!autoload) return { autoload: false } return { @@ -361,9 +368,26 @@ export namespace Provider { options: { project, location, + baseURL: + location === "global" + ? `https://aiplatform.googleapis.com/v1/projects/${project}/locations/${location}/endpoints/openapi` + : `https://${location}-aiplatform.googleapis.com/v1/projects/${project}/locations/${location}/endpoints/openapi`, + fetch: async (input: RequestInfo | URL, init?: RequestInit) => { + const { GoogleAuth } = await import(await BunProc.install("google-auth-library")) + const auth = new GoogleAuth() + const client = await auth.getApplicationDefault() + const credentials = await client.credential + const token = await credentials.getAccessToken() + + const headers = new Headers(init?.headers) + headers.set("Authorization", `Bearer ${token.token}`) + + return fetch(input, { ...init, headers }) + }, }, async getModel(sdk: any, modelID: string) { const id = String(modelID).trim() + if (typeof sdk === "function") return sdk(id) return sdk.languageModel(id) }, } @@ -992,6 +1016,15 @@ export namespace Provider { const provider = s.providers[model.providerID] const options = { ...provider.options } + if (model.api.npm === "@ai-sdk/google-vertex" || model.api.npm === "@ai-sdk/google") { + const isGoogle = !options.baseURL || options.baseURL.includes("googleapis.com") + if (isGoogle) { + delete options.baseURL + delete options.fetch + delete options.headers + } + } + if (model.api.npm.includes("@ai-sdk/openai-compatible") && options["includeUsage"] !== false) { options["includeUsage"] = true } diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 98cd49c02fd3..87ff9d9d07f0 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -2127,3 +2127,95 @@ test("custom model with variants enabled and disabled", async () => { }, }) }) + +test("Google Vertex: retains baseURL for custom proxy", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "vertex-proxy": { + name: "Vertex Proxy", + npm: "@ai-sdk/google-vertex", + api: "https://my-proxy.com/v1", + env: ["GOOGLE_APPLICATION_CREDENTIALS"], // Mock env var requirement + models: { + "gemini-pro": { + name: "Gemini Pro", + tool_call: true, + }, + }, + options: { + project: "test-project", + location: "us-central1", + baseURL: "https://my-proxy.com/v1", // Should be retained + }, + }, + }, + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") + }, + fn: async () => { + const providers = await Provider.list() + expect(providers["vertex-proxy"]).toBeDefined() + expect(providers["vertex-proxy"].options.baseURL).toBe("https://my-proxy.com/v1") + }, + }) +}) + +test("Google Vertex: supports OpenAI compatible models", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "vertex-openai": { + name: "Vertex OpenAI", + npm: "@ai-sdk/google-vertex", + env: ["GOOGLE_APPLICATION_CREDENTIALS"], + models: { + "gpt-4": { + name: "GPT-4", + provider: { + npm: "@ai-sdk/openai-compatible", + api: "https://api.openai.com/v1", + }, + }, + }, + options: { + project: "test-project", + location: "us-central1", + }, + }, + }, + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") + }, + fn: async () => { + const providers = await Provider.list() + const model = providers["vertex-openai"].models["gpt-4"] + + expect(model).toBeDefined() + expect(model.api.npm).toBe("@ai-sdk/openai-compatible") + }, + }) +}) +