From 906dc8f5b2d101b707535ea5621fe9b841659ad8 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 29 Jul 2026 22:42:47 -0500 Subject: [PATCH] fix(ai): apply catalog settings to provider models (#39613) --- packages/ai/src/providers/openrouter.ts | 20 ++++++++++++++- packages/ai/src/providers/xai.ts | 17 ++++++++++++- packages/ai/test/exports.test.ts | 2 -- packages/ai/test/provider-package.test.ts | 31 +++++++++++++++++++++++ 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/packages/ai/src/providers/openrouter.ts b/packages/ai/src/providers/openrouter.ts index 4bcdb3f9db..29f75b409e 100644 --- a/packages/ai/src/providers/openrouter.ts +++ b/packages/ai/src/providers/openrouter.ts @@ -5,6 +5,7 @@ import { Framing } from "../route/framing" import { Protocol } from "../route/protocol" import { AuthOptions, type ProviderAuthOption } from "../route/auth-options" import { ProviderID, type ModelID, type ProviderOptions } from "../schema" +import type { ProviderPackage } from "../provider-package" import * as OpenAICompatibleProfiles from "./openai-compatible-profile" import * as OpenAIChat from "../protocols/openai-chat" import { isRecord } from "../protocols/shared" @@ -30,6 +31,12 @@ export type ModelOptions = Omit & readonly providerOptions?: OpenRouterProviderOptionsInput } +export interface Settings extends ProviderPackage.Settings { + readonly apiKey?: string + readonly baseURL?: string + readonly providerOptions?: OpenRouterProviderOptionsInput +} + const OpenRouterBody = Schema.StructWithRest(Schema.Struct(OpenAIChat.bodyFields), [ Schema.Record(Schema.String, Schema.Any), ]) @@ -113,4 +120,15 @@ export const configure = (input: ModelOptions = {}) => { } export const provider = configure() -export const model = provider.model +export const model: ProviderPackage.Definition["model"] = ( + modelID, + settings, +) => + configure({ + apiKey: settings.apiKey, + baseURL: settings.baseURL, + headers: settings.headers, + http: settings.body === undefined ? undefined : { body: { ...settings.body } }, + limits: settings.limits, + providerOptions: settings.providerOptions, + }).model(modelID) diff --git a/packages/ai/src/providers/xai.ts b/packages/ai/src/providers/xai.ts index 37aae83a52..281a106357 100644 --- a/packages/ai/src/providers/xai.ts +++ b/packages/ai/src/providers/xai.ts @@ -6,6 +6,7 @@ import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat" import * as OpenAIResponses from "../protocols/openai-responses" import { XAIImages } from "../protocols/xai-images" import type { OpenAIProviderOptionsInput } from "./openai-options" +import type { ProviderPackage } from "../provider-package" export const id = ProviderID.make("xai") @@ -15,6 +16,12 @@ export type ModelOptions = Omit & readonly providerOptions?: OpenAIProviderOptionsInput } +export interface Settings extends ProviderPackage.Settings { + readonly apiKey?: string + readonly baseURL?: string + readonly providerOptions?: OpenAIProviderOptionsInput +} + export type { XAIImageOptions } from "../protocols/xai-images" export const routes = [OpenAIResponses.route, OpenAICompatibleChat.route] @@ -65,7 +72,15 @@ export const configure = (input: ModelOptions = {}) => { } export const provider = configure() -export const model = provider.model +export const model: ProviderPackage.Definition["model"] = (modelID, settings) => + configure({ + apiKey: settings.apiKey, + baseURL: settings.baseURL, + headers: settings.headers, + http: settings.body === undefined ? undefined : { body: { ...settings.body } }, + limits: settings.limits, + providerOptions: settings.providerOptions, + }).model(modelID) export const responses = provider.responses export const chat = provider.chat export const image = provider.image diff --git a/packages/ai/test/exports.test.ts b/packages/ai/test/exports.test.ts index cb32528634..d036392278 100644 --- a/packages/ai/test/exports.test.ts +++ b/packages/ai/test/exports.test.ts @@ -53,9 +53,7 @@ describe("public exports", () => { expect(CloudflareWorkersAI.configure).toBeFunction() expect(CloudflareWorkersAI.configure({ accountId: "fixture", apiKey: "fixture" }).model).toBeFunction() expect(OpenRouter.model).toBeFunction() - expect(OpenRouter.provider.model).toBe(OpenRouter.model) expect(XAI.model).toBeFunction() - expect(XAI.provider.model).toBe(XAI.model) expect(XAI.provider.responses).toBe(XAI.responses) expect(XAI.provider.chat).toBe(XAI.chat) expect(XAI.configure({ apiKey: "fixture" }).responses("grok-4.3").route.id).toBe("openai-responses") diff --git a/packages/ai/test/provider-package.test.ts b/packages/ai/test/provider-package.test.ts index 45ab7dfa84..0bb1b1441b 100644 --- a/packages/ai/test/provider-package.test.ts +++ b/packages/ai/test/provider-package.test.ts @@ -21,6 +21,8 @@ describe("provider package entrypoints", () => { import("@opencode-ai/ai/providers/google-vertex/chat"), import("@opencode-ai/ai/providers/google-vertex/responses"), import("@opencode-ai/ai/providers/google-vertex/messages"), + import("@opencode-ai/ai/providers/openrouter"), + import("@opencode-ai/ai/providers/xai"), ]) for (const module of modules) expect(module.model).toBeFunction() @@ -29,6 +31,35 @@ describe("provider package entrypoints", () => { expect(modules[12].model).toBe(modules[13].model) }) + test("maps OpenRouter and xAI package settings onto executable models", async () => { + const OpenRouter = await import("@opencode-ai/ai/providers/openrouter") + const XAI = await import("@opencode-ai/ai/providers/xai") + const settings = { + apiKey: "fixture", + baseURL: "https://provider.example.test/v1", + headers: { "x-application": "opencode" }, + body: { service_tier: "priority" }, + limits: { context: 200_000, output: 64_000 }, + } + const openrouter = OpenRouter.model("anthropic/claude-sonnet-4", { + ...settings, + providerOptions: { openrouter: { usage: true } }, + }) + const xai = XAI.model("grok-4", { + ...settings, + providerOptions: { openai: { reasoningEffort: "high" } }, + }) + + for (const selected of [openrouter, xai]) { + expect(selected.route.endpoint.baseURL).toBe(settings.baseURL) + expect(selected.route.defaults.headers).toEqual(settings.headers) + expect(selected.route.defaults.http?.body).toEqual(settings.body) + expect(selected.route.defaults.limits).toEqual(settings.limits) + } + expect(openrouter.route.defaults.providerOptions).toEqual({ openrouter: { usage: true } }) + expect(xai.route.defaults.providerOptions).toEqual({ openai: { reasoningEffort: "high", store: false } }) + }) + test("maps package settings onto the executable model", () => { const selected = model("gpt-5", { apiKey: "fixture",