Skip to content

Commit f7708ef

Browse files
leehackbluetrekram1-node
authored
feat: add openai-compatible endpoint support for google-vertex provider (#10303)
Co-authored-by: BlueT - Matthew Lien - 練喆明 <BlueT@BlueT.org> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
1 parent 6080784 commit f7708ef

2 files changed

Lines changed: 153 additions & 4 deletions

File tree

packages/opencode/src/provider/provider.ts

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,39 @@ export namespace Provider {
5757
return isGpt5OrLater(modelID) && !modelID.startsWith("gpt-5-mini")
5858
}
5959

60+
function googleVertexVars(options: Record<string, any>) {
61+
const project =
62+
Env.get("GOOGLE_VERTEX_PROJECT") ??
63+
options["project"] ??
64+
Env.get("GOOGLE_CLOUD_PROJECT") ??
65+
Env.get("GCP_PROJECT") ??
66+
Env.get("GCLOUD_PROJECT")
67+
const location =
68+
Env.get("GOOGLE_VERTEX_LOCATION") ??
69+
options["location"] ??
70+
Env.get("GOOGLE_CLOUD_LOCATION") ??
71+
Env.get("VERTEX_LOCATION") ??
72+
"us-central1"
73+
const endpoint =
74+
Env.get("GOOGLE_VERTEX_ENDPOINT") ??
75+
(location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`)
76+
return {
77+
GOOGLE_VERTEX_PROJECT: project,
78+
GOOGLE_VERTEX_LOCATION: location,
79+
GOOGLE_VERTEX_ENDPOINT: endpoint,
80+
}
81+
}
82+
83+
function loadBaseURL(model: Model, options: Record<string, any>) {
84+
const raw = options["baseURL"] ?? model.api.url
85+
if (typeof raw !== "string") return raw
86+
const vars = model.providerID === "google-vertex" ? googleVertexVars(options) : undefined
87+
return raw.replace(/\$\{([^}]+)\}/g, (match, key) => {
88+
const val = Env.get(String(key)) ?? vars?.[String(key) as keyof typeof vars]
89+
return val ?? match
90+
})
91+
}
92+
6093
const BUNDLED_PROVIDERS: Record<string, (options: any) => SDK> = {
6194
"@ai-sdk/amazon-bedrock": createAmazonBedrock,
6295
"@ai-sdk/anthropic": createAnthropic,
@@ -353,16 +386,35 @@ export namespace Provider {
353386
},
354387
}
355388
},
356-
"google-vertex": async () => {
357-
const project = Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT")
358-
const location = Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-east5"
389+
"google-vertex": async (provider) => {
390+
const project =
391+
provider.options?.project ??
392+
Env.get("GOOGLE_CLOUD_PROJECT") ??
393+
Env.get("GCP_PROJECT") ??
394+
Env.get("GCLOUD_PROJECT")
395+
396+
const location =
397+
provider.options?.location ?? Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-central1"
398+
359399
const autoload = Boolean(project)
360400
if (!autoload) return { autoload: false }
361401
return {
362402
autoload: true,
363403
options: {
364404
project,
365405
location,
406+
fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
407+
const { GoogleAuth } = await import(await BunProc.install("google-auth-library"))
408+
const auth = new GoogleAuth()
409+
const client = await auth.getApplicationDefault()
410+
const credentials = await client.credential
411+
const token = await credentials.getAccessToken()
412+
413+
const headers = new Headers(init?.headers)
414+
headers.set("Authorization", `Bearer ${token.token}`)
415+
416+
return fetch(input, { ...init, headers })
417+
},
366418
},
367419
async getModel(sdk: any, modelID: string) {
368420
const id = String(modelID).trim()
@@ -994,11 +1046,16 @@ export namespace Provider {
9941046
const provider = s.providers[model.providerID]
9951047
const options = { ...provider.options }
9961048

1049+
if (model.providerID === "google-vertex" && !model.api.npm.includes("@ai-sdk/openai-compatible")) {
1050+
delete options.fetch
1051+
}
1052+
9971053
if (model.api.npm.includes("@ai-sdk/openai-compatible") && options["includeUsage"] !== false) {
9981054
options["includeUsage"] = true
9991055
}
10001056

1001-
if (!options["baseURL"]) options["baseURL"] = model.api.url
1057+
const baseURL = loadBaseURL(model, options)
1058+
if (baseURL !== undefined) options["baseURL"] = baseURL
10021059
if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
10031060
if (model.headers)
10041061
options["headers"] = {

packages/opencode/test/provider/provider.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2127,3 +2127,95 @@ test("custom model with variants enabled and disabled", async () => {
21272127
},
21282128
})
21292129
})
2130+
2131+
test("Google Vertex: retains baseURL for custom proxy", async () => {
2132+
await using tmp = await tmpdir({
2133+
init: async (dir) => {
2134+
await Bun.write(
2135+
path.join(dir, "opencode.json"),
2136+
JSON.stringify({
2137+
$schema: "https://opencode.ai/config.json",
2138+
provider: {
2139+
"vertex-proxy": {
2140+
name: "Vertex Proxy",
2141+
npm: "@ai-sdk/google-vertex",
2142+
api: "https://my-proxy.com/v1",
2143+
env: ["GOOGLE_APPLICATION_CREDENTIALS"], // Mock env var requirement
2144+
models: {
2145+
"gemini-pro": {
2146+
name: "Gemini Pro",
2147+
tool_call: true,
2148+
},
2149+
},
2150+
options: {
2151+
project: "test-project",
2152+
location: "us-central1",
2153+
baseURL: "https://my-proxy.com/v1", // Should be retained
2154+
},
2155+
},
2156+
},
2157+
}),
2158+
)
2159+
},
2160+
})
2161+
2162+
await Instance.provide({
2163+
directory: tmp.path,
2164+
init: async () => {
2165+
Env.set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
2166+
},
2167+
fn: async () => {
2168+
const providers = await Provider.list()
2169+
expect(providers["vertex-proxy"]).toBeDefined()
2170+
expect(providers["vertex-proxy"].options.baseURL).toBe("https://my-proxy.com/v1")
2171+
},
2172+
})
2173+
})
2174+
2175+
test("Google Vertex: supports OpenAI compatible models", async () => {
2176+
await using tmp = await tmpdir({
2177+
init: async (dir) => {
2178+
await Bun.write(
2179+
path.join(dir, "opencode.json"),
2180+
JSON.stringify({
2181+
$schema: "https://opencode.ai/config.json",
2182+
provider: {
2183+
"vertex-openai": {
2184+
name: "Vertex OpenAI",
2185+
npm: "@ai-sdk/google-vertex",
2186+
env: ["GOOGLE_APPLICATION_CREDENTIALS"],
2187+
models: {
2188+
"gpt-4": {
2189+
name: "GPT-4",
2190+
provider: {
2191+
npm: "@ai-sdk/openai-compatible",
2192+
api: "https://api.openai.com/v1",
2193+
},
2194+
},
2195+
},
2196+
options: {
2197+
project: "test-project",
2198+
location: "us-central1",
2199+
},
2200+
},
2201+
},
2202+
}),
2203+
)
2204+
},
2205+
})
2206+
2207+
await Instance.provide({
2208+
directory: tmp.path,
2209+
init: async () => {
2210+
Env.set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
2211+
},
2212+
fn: async () => {
2213+
const providers = await Provider.list()
2214+
const model = providers["vertex-openai"].models["gpt-4"]
2215+
2216+
expect(model).toBeDefined()
2217+
expect(model.api.npm).toBe("@ai-sdk/openai-compatible")
2218+
},
2219+
})
2220+
})
2221+

0 commit comments

Comments
 (0)