From 9c657f703b4f34f32220a57579776cb75cb5d881 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Tue, 10 Mar 2026 22:36:28 -0500 Subject: [PATCH 1/8] tweak: adjust getModel logic to be more reliable --- packages/opencode/src/provider/provider.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index c174ebd9ffa..6fa617557f5 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -167,6 +167,10 @@ export namespace Provider { options?: Record }> + function useLanguageModel(sdk: any) { + return sdk.responses === undefined && sdk.chat === undefined + } + const CUSTOM_LOADERS: Record = { async anthropic() { return { @@ -214,7 +218,7 @@ export namespace Provider { return { autoload: false, async getModel(sdk: any, modelID: string, _options?: Record) { - if (sdk.responses === undefined && sdk.chat === undefined) return sdk.languageModel(modelID) + if (useLanguageModel(sdk)) return sdk.languageModel(modelID) return shouldUseCopilotResponsesApi(modelID) ? sdk.responses(modelID) : sdk.chat(modelID) }, options: {}, @@ -224,7 +228,7 @@ export namespace Provider { return { autoload: false, async getModel(sdk: any, modelID: string, _options?: Record) { - if (sdk.responses === undefined && sdk.chat === undefined) return sdk.languageModel(modelID) + if (useLanguageModel(sdk)) return sdk.languageModel(modelID) return shouldUseCopilotResponsesApi(modelID) ? sdk.responses(modelID) : sdk.chat(modelID) }, options: {}, @@ -234,6 +238,7 @@ export namespace Provider { return { autoload: false, async getModel(sdk: any, modelID: string, options?: Record) { + if (useLanguageModel(sdk)) return sdk.languageModel(modelID) if (options?.["useCompletionUrls"]) { return sdk.chat(modelID) } else { @@ -248,6 +253,7 @@ export namespace Provider { return { autoload: false, async getModel(sdk: any, modelID: string, options?: Record) { + if (useLanguageModel(sdk)) return sdk.languageModel(modelID) if (options?.["useCompletionUrls"]) { return sdk.chat(modelID) } else { From a4efdb88252b747f99267cc624f4f3f4520793cd Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 11 Mar 2026 11:51:25 -0500 Subject: [PATCH 2/8] checkpoint --- .opencode/opencode.jsonc | 18 +++++ packages/opencode/src/provider/provider.ts | 92 +++++++++++----------- test.ts | 37 +++++++++ 3 files changed, 101 insertions(+), 46 deletions(-) create mode 100644 test.ts diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index 8380f7f719e..4c38b7d3ec1 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -4,6 +4,24 @@ "opencode": { "options": {}, }, + "azure": { + "models": { + "kimi-k2.5": { + "name": "kimi-k2.5", + "limit": { + "context": 262144, + "output": 10000, + }, + "provider": { + "npm": "@ai-sdk/openai-compatible", + "api": "https://aiden-azury-group.services.ai.azure.com/models", + }, + }, + }, + "options": { + "resourceName": "aiden-azury-group", + }, + }, }, "permission": { "edit": { diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 58fdc0f987e..9a06c130ef1 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -51,44 +51,15 @@ const DEFAULT_CHUNK_TIMEOUT = 120_000 export namespace Provider { const log = Log.create({ service: "provider" }) - function isGpt5OrLater(modelID: string): boolean { - const match = /^gpt-(\d+)/.exec(modelID) - if (!match) { - return false - } - return Number(match[1]) >= 5 - } - function shouldUseCopilotResponsesApi(modelID: string): boolean { - return isGpt5OrLater(modelID) && !modelID.startsWith("gpt-5-mini") - } - - function googleVertexVars(options: Record) { - const project = - options["project"] ?? Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT") - const location = - options["location"] ?? - Env.get("GOOGLE_VERTEX_LOCATION") ?? - Env.get("GOOGLE_CLOUD_LOCATION") ?? - Env.get("VERTEX_LOCATION") ?? - "us-central1" - const 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) { - 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 isGpt5OrLater = iife(() => { + const match = /^gpt-(\d+)/.exec(modelID) + if (!match) { + return false + } + return Number(match[1]) >= 5 }) + return isGpt5OrLater && !modelID.startsWith("gpt-5-mini") } function wrapSSE(res: Response, ms: number, ctl: AbortController) { @@ -165,9 +136,11 @@ export namespace Provider { } type CustomModelLoader = (sdk: any, modelID: string, options?: Record) => Promise + type CustomVarsLoader = (options: Record) => Record type CustomLoader = (provider: Info) => Promise<{ autoload: boolean getModel?: CustomModelLoader + vars?: CustomVarsLoader options?: Record }> @@ -440,23 +413,33 @@ export namespace Provider { } }, "google-vertex": async (provider) => { - const project = + const project = String( provider.options?.project ?? - Env.get("GOOGLE_CLOUD_PROJECT") ?? - Env.get("GCP_PROJECT") ?? - Env.get("GCLOUD_PROJECT") + Env.get("GOOGLE_CLOUD_PROJECT") ?? + Env.get("GCP_PROJECT") ?? + Env.get("GCLOUD_PROJECT"), + ) - const location = + const location = String( provider.options?.location ?? - Env.get("GOOGLE_VERTEX_LOCATION") ?? - Env.get("GOOGLE_CLOUD_LOCATION") ?? - Env.get("VERTEX_LOCATION") ?? - "us-central1" + Env.get("GOOGLE_VERTEX_LOCATION") ?? + Env.get("GOOGLE_CLOUD_LOCATION") ?? + Env.get("VERTEX_LOCATION") ?? + "us-central1", + ) const autoload = Boolean(project) if (!autoload) return { autoload: false } return { autoload: true, + vars(_options: Record) { + const endpoint = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com` + return { + GOOGLE_VERTEX_PROJECT: project, + GOOGLE_VERTEX_LOCATION: location, + GOOGLE_VERTEX_ENDPOINT: endpoint, + } + }, options: { project, location, @@ -861,6 +844,9 @@ export namespace Provider { const modelLoaders: { [providerID: string]: CustomModelLoader } = {} + const varsLoaders: { + [providerID: string]: CustomVarsLoader + } = {} const sdk = new Map() log.info("init") @@ -1057,6 +1043,7 @@ export namespace Provider { const result = await fn(data) if (result && (result.autoload || providers[providerID])) { if (result.getModel) modelLoaders[providerID] = result.getModel + if (result.vars) varsLoaders[providerID] = result.vars const opts = result.options ?? {} const patch: Partial = providers[providerID] ? { options: opts } : { source: "custom", options: opts } mergeProvider(providerID, patch) @@ -1118,6 +1105,7 @@ export namespace Provider { providers, sdk, modelLoaders, + varsLoaders, } }) @@ -1142,7 +1130,19 @@ export namespace Provider { options["includeUsage"] = true } - const baseURL = loadBaseURL(model, options) + const baseURL = iife(() => { + let url = String(options["baseURL"]) || model.api.url + const loader = s.varsLoaders[model.providerID] + if (loader) { + const vars = loader(options) + for (const [key, value] of Object.entries(vars)) { + const field = `\$\{${key}\}` + url = url.replaceAll(field, value) + } + } + return url + }) + if (baseURL !== undefined) options["baseURL"] = baseURL if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key if (model.headers) diff --git a/test.ts b/test.ts new file mode 100644 index 00000000000..6206b8dde9a --- /dev/null +++ b/test.ts @@ -0,0 +1,37 @@ +import { text } from "node:stream/consumers" +import { Process } from "./packages/opencode/src/util/process" + +function run() { + return Process.spawn([process.execPath, "info", "semver", "version"], { + stdout: "pipe", + stderr: "pipe", + env: { + ...process.env, + BUN_BE_BUN: "1", + }, + }) +} + +async function old() { + const proc = run() + const code = await proc.exited + const stdout = proc.stdout ? await text(proc.stdout) : "" + const stderr = proc.stderr ? await text(proc.stderr) : "" + return { code, stdout: stdout.trim(), stderr: stderr.trim() } +} + +async function next() { + const out = await Process.text([process.execPath, "info", "semver", "version"], { + env: { + ...process.env, + BUN_BE_BUN: "1", + }, + }) + return { code: out.code, stdout: out.stdout.toString().trim(), stderr: out.stderr.toString().trim() } +} + +const [a, b] = await Promise.all([old(), next()]) + +console.log("old", a) +console.log("new", b) +console.log("reproduced", a.stdout.length === 0 && b.stdout.length > 0) From 5ca53a8145e3c2bce87752f38db84a44919fd64c Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 11 Mar 2026 12:55:05 -0500 Subject: [PATCH 3/8] minor cleanup --- packages/opencode/src/provider/provider.ts | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 9a06c130ef1..7fdb151c57b 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -413,12 +413,11 @@ export namespace Provider { } }, "google-vertex": async (provider) => { - const project = String( + const project = provider.options?.project ?? - Env.get("GOOGLE_CLOUD_PROJECT") ?? - Env.get("GCP_PROJECT") ?? - Env.get("GCLOUD_PROJECT"), - ) + Env.get("GOOGLE_CLOUD_PROJECT") ?? + Env.get("GCP_PROJECT") ?? + Env.get("GCLOUD_PROJECT") const location = String( provider.options?.location ?? @@ -571,11 +570,15 @@ export namespace Provider { autoload: !!apiKey, options: { apiKey, - baseURL: `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`, }, async getModel(sdk: any, modelID: string) { return sdk.languageModel(modelID) }, + vars(_options) { + return { + CLOUDFLARE_ACCOUNT_ID: accountId, + } + }, } }, "cloudflare-ai-gateway": async (input) => { @@ -1131,7 +1134,13 @@ export namespace Provider { } const baseURL = iife(() => { - let url = String(options["baseURL"]) || model.api.url + let url = + typeof options["baseURL"] === "string" && options["baseURL"] !== "" ? options["baseURL"] : model.api.url + if (!url) return + + // some models/providers have variable urls, ex: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1" + // We track this in models.dev, and then when we are resolving the baseURL + // we need to string replace that literal: "${AZURE_RESOURCE_NAME}" const loader = s.varsLoaders[model.providerID] if (loader) { const vars = loader(options) From 39353b58f1eada6892fbe7e2770735ef8eda9f97 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 11 Mar 2026 22:56:53 -0500 Subject: [PATCH 4/8] sync --- .opencode/opencode.jsonc | 18 ------------------ packages/opencode/src/provider/provider.ts | 9 ++++++++- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index 4c38b7d3ec1..8380f7f719e 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -4,24 +4,6 @@ "opencode": { "options": {}, }, - "azure": { - "models": { - "kimi-k2.5": { - "name": "kimi-k2.5", - "limit": { - "context": 262144, - "output": 10000, - }, - "provider": { - "npm": "@ai-sdk/openai-compatible", - "api": "https://aiden-azury-group.services.ai.azure.com/models", - }, - }, - }, - "options": { - "resourceName": "aiden-azury-group", - }, - }, }, "permission": { "edit": { diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 7fdb151c57b..22591537e33 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -211,7 +211,9 @@ export namespace Provider { options: {}, } }, - azure: async () => { + azure: async (provider) => { + const resource = provider.options?.resourceName ?? Env.get("AZURE_RESOURCE_NAME") + return { autoload: false, async getModel(sdk: any, modelID: string, options?: Record) { @@ -223,6 +225,11 @@ export namespace Provider { } }, options: {}, + vars(_options) { + return { + AZURE_RESOURCE_NAME: resource, + } + }, } }, "azure-cognitive-services": async () => { From d4628788c7f7579ef390c0b78ebc8e73edc1e703 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 11 Mar 2026 23:41:43 -0500 Subject: [PATCH 5/8] cleanup --- packages/opencode/src/provider/provider.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 22591537e33..962b95aeeb8 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -212,7 +212,11 @@ export namespace Provider { } }, azure: async (provider) => { - const resource = provider.options?.resourceName ?? Env.get("AZURE_RESOURCE_NAME") + const resource = iife(() => { + const name = provider.options?.resourceName + if (typeof name === "string" && name.trim() !== "") return name + return Env.get("AZURE_RESOURCE_NAME") + }) return { autoload: false, @@ -227,7 +231,7 @@ export namespace Provider { options: {}, vars(_options) { return { - AZURE_RESOURCE_NAME: resource, + ...(resource && { AZURE_RESOURCE_NAME: resource }), } }, } @@ -441,7 +445,7 @@ export namespace Provider { vars(_options: Record) { const endpoint = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com` return { - GOOGLE_VERTEX_PROJECT: project, + ...(project && { GOOGLE_VERTEX_PROJECT: project }), GOOGLE_VERTEX_LOCATION: location, GOOGLE_VERTEX_ENDPOINT: endpoint, } @@ -1156,6 +1160,11 @@ export namespace Provider { url = url.replaceAll(field, value) } } + + url = url.replace(/\$\{([^}]+)\}/g, (item, key) => { + const val = Env.get(String(key)) + return val ?? item + }) return url }) From f912a4bddb6f6b72c3b77f7cc2e0598ed72406f9 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 11 Mar 2026 23:42:41 -0500 Subject: [PATCH 6/8] cleanup --- test.ts | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 test.ts diff --git a/test.ts b/test.ts deleted file mode 100644 index 6206b8dde9a..00000000000 --- a/test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { text } from "node:stream/consumers" -import { Process } from "./packages/opencode/src/util/process" - -function run() { - return Process.spawn([process.execPath, "info", "semver", "version"], { - stdout: "pipe", - stderr: "pipe", - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - }) -} - -async function old() { - const proc = run() - const code = await proc.exited - const stdout = proc.stdout ? await text(proc.stdout) : "" - const stderr = proc.stderr ? await text(proc.stderr) : "" - return { code, stdout: stdout.trim(), stderr: stderr.trim() } -} - -async function next() { - const out = await Process.text([process.execPath, "info", "semver", "version"], { - env: { - ...process.env, - BUN_BE_BUN: "1", - }, - }) - return { code: out.code, stdout: out.stdout.toString().trim(), stderr: out.stderr.toString().trim() } -} - -const [a, b] = await Promise.all([old(), next()]) - -console.log("old", a) -console.log("new", b) -console.log("reproduced", a.stdout.length === 0 && b.stdout.length > 0) From f1e058801cbb538047c4ef6ac26a2edf6ec98f18 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 12 Mar 2026 00:15:48 -0500 Subject: [PATCH 7/8] nit: use more idiomatic line --- packages/opencode/src/provider/provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 962b95aeeb8..4b00df35d16 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1156,7 +1156,7 @@ export namespace Provider { if (loader) { const vars = loader(options) for (const [key, value] of Object.entries(vars)) { - const field = `\$\{${key}\}` + const field = "${" + key + "}" url = url.replaceAll(field, value) } } From fab930e9f0220c155ae61084e48b4dce97846078 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 12 Mar 2026 00:21:57 -0500 Subject: [PATCH 8/8] nit: cleanup again --- packages/opencode/src/provider/provider.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 4b00df35d16..74d6a9deb0b 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -52,14 +52,9 @@ export namespace Provider { const log = Log.create({ service: "provider" }) function shouldUseCopilotResponsesApi(modelID: string): boolean { - const isGpt5OrLater = iife(() => { - const match = /^gpt-(\d+)/.exec(modelID) - if (!match) { - return false - } - return Number(match[1]) >= 5 - }) - return isGpt5OrLater && !modelID.startsWith("gpt-5-mini") + const match = /^gpt-(\d+)/.exec(modelID) + if (!match) return false + return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini") } function wrapSSE(res: Response, ms: number, ctl: AbortController) {