summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJhin Lee <[email protected]>2026-02-16 03:31:48 -0500
committerGitHub <[email protected]>2026-02-16 02:31:48 -0600
commitf7708efa5b87ae292c973d3fb409d060b5ed8f56 (patch)
treeda48bdb4ea0cb644675398779b573cb6cb53e085
parent60807846a92be5ab75367d8ca14b6b1bc697aebe (diff)
downloadopencode-f7708efa5b87ae292c973d3fb409d060b5ed8f56.tar.gz
opencode-f7708efa5b87ae292c973d3fb409d060b5ed8f56.zip
feat: add openai-compatible endpoint support for google-vertex provider (#10303)
Co-authored-by: BlueT - Matthew Lien - 練喆明 <[email protected]> Co-authored-by: Aiden Cline <[email protected]> Co-authored-by: Aiden Cline <[email protected]>
-rw-r--r--packages/opencode/src/provider/provider.ts65
-rw-r--r--packages/opencode/test/provider/provider.test.ts92
2 files changed, 153 insertions, 4 deletions
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 44bcf8adb..e2385f1ac 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -57,6 +57,39 @@ export namespace Provider {
return isGpt5OrLater(modelID) && !modelID.startsWith("gpt-5-mini")
}
+ function googleVertexVars(options: Record<string, any>) {
+ const project =
+ Env.get("GOOGLE_VERTEX_PROJECT") ??
+ options["project"] ??
+ Env.get("GOOGLE_CLOUD_PROJECT") ??
+ Env.get("GCP_PROJECT") ??
+ Env.get("GCLOUD_PROJECT")
+ const location =
+ Env.get("GOOGLE_VERTEX_LOCATION") ??
+ options["location"] ??
+ Env.get("GOOGLE_CLOUD_LOCATION") ??
+ Env.get("VERTEX_LOCATION") ??
+ "us-central1"
+ const endpoint =
+ Env.get("GOOGLE_VERTEX_ENDPOINT") ??
+ (location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`)
+ return {
+ GOOGLE_VERTEX_PROJECT: project,
+ GOOGLE_VERTEX_LOCATION: location,
+ GOOGLE_VERTEX_ENDPOINT: endpoint,
+ }
+ }
+
+ function loadBaseURL(model: Model, options: Record<string, any>) {
+ const raw = options["baseURL"] ?? model.api.url
+ if (typeof raw !== "string") return raw
+ const vars = model.providerID === "google-vertex" ? googleVertexVars(options) : undefined
+ return raw.replace(/\$\{([^}]+)\}/g, (match, key) => {
+ const val = Env.get(String(key)) ?? vars?.[String(key) as keyof typeof vars]
+ return val ?? match
+ })
+ }
+
const BUNDLED_PROVIDERS: Record<string, (options: any) => SDK> = {
"@ai-sdk/amazon-bedrock": createAmazonBedrock,
"@ai-sdk/anthropic": createAnthropic,
@@ -353,9 +386,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 {
@@ -363,6 +403,18 @@ export namespace Provider {
options: {
project,
location,
+ 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()
@@ -994,11 +1046,16 @@ export namespace Provider {
const provider = s.providers[model.providerID]
const options = { ...provider.options }
+ if (model.providerID === "google-vertex" && !model.api.npm.includes("@ai-sdk/openai-compatible")) {
+ delete options.fetch
+ }
+
if (model.api.npm.includes("@ai-sdk/openai-compatible") && options["includeUsage"] !== false) {
options["includeUsage"] = true
}
- if (!options["baseURL"]) options["baseURL"] = model.api.url
+ const baseURL = loadBaseURL(model, options)
+ if (baseURL !== undefined) options["baseURL"] = baseURL
if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
if (model.headers)
options["headers"] = {
diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts
index 98cd49c02..87ff9d9d0 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")
+ },
+ })
+})
+