Skip to content

Commit 906dc8f

Browse files
authored
fix(ai): apply catalog settings to provider models (#39613)
1 parent 1297193 commit 906dc8f

4 files changed

Lines changed: 66 additions & 4 deletions

File tree

packages/ai/src/providers/openrouter.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Framing } from "../route/framing"
55
import { Protocol } from "../route/protocol"
66
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
77
import { ProviderID, type ModelID, type ProviderOptions } from "../schema"
8+
import type { ProviderPackage } from "../provider-package"
89
import * as OpenAICompatibleProfiles from "./openai-compatible-profile"
910
import * as OpenAIChat from "../protocols/openai-chat"
1011
import { isRecord } from "../protocols/shared"
@@ -30,6 +31,12 @@ export type ModelOptions = Omit<RouteDefaultsInput, "providerOptions"> &
3031
readonly providerOptions?: OpenRouterProviderOptionsInput
3132
}
3233

34+
export interface Settings extends ProviderPackage.Settings {
35+
readonly apiKey?: string
36+
readonly baseURL?: string
37+
readonly providerOptions?: OpenRouterProviderOptionsInput
38+
}
39+
3340
const OpenRouterBody = Schema.StructWithRest(Schema.Struct(OpenAIChat.bodyFields), [
3441
Schema.Record(Schema.String, Schema.Any),
3542
])
@@ -113,4 +120,15 @@ export const configure = (input: ModelOptions = {}) => {
113120
}
114121

115122
export const provider = configure()
116-
export const model = provider.model
123+
export const model: ProviderPackage.Definition<Settings, OpenRouterProviderOptionsInput>["model"] = (
124+
modelID,
125+
settings,
126+
) =>
127+
configure({
128+
apiKey: settings.apiKey,
129+
baseURL: settings.baseURL,
130+
headers: settings.headers,
131+
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
132+
limits: settings.limits,
133+
providerOptions: settings.providerOptions,
134+
}).model(modelID)

packages/ai/src/providers/xai.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
66
import * as OpenAIResponses from "../protocols/openai-responses"
77
import { XAIImages } from "../protocols/xai-images"
88
import type { OpenAIProviderOptionsInput } from "./openai-options"
9+
import type { ProviderPackage } from "../provider-package"
910

1011
export const id = ProviderID.make("xai")
1112

@@ -15,6 +16,12 @@ export type ModelOptions = Omit<RouteDefaultsInput, "providerOptions"> &
1516
readonly providerOptions?: OpenAIProviderOptionsInput
1617
}
1718

19+
export interface Settings extends ProviderPackage.Settings {
20+
readonly apiKey?: string
21+
readonly baseURL?: string
22+
readonly providerOptions?: OpenAIProviderOptionsInput
23+
}
24+
1825
export type { XAIImageOptions } from "../protocols/xai-images"
1926

2027
export const routes = [OpenAIResponses.route, OpenAICompatibleChat.route]
@@ -65,7 +72,15 @@ export const configure = (input: ModelOptions = {}) => {
6572
}
6673

6774
export const provider = configure()
68-
export const model = provider.model
75+
export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"] = (modelID, settings) =>
76+
configure({
77+
apiKey: settings.apiKey,
78+
baseURL: settings.baseURL,
79+
headers: settings.headers,
80+
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
81+
limits: settings.limits,
82+
providerOptions: settings.providerOptions,
83+
}).model(modelID)
6984
export const responses = provider.responses
7085
export const chat = provider.chat
7186
export const image = provider.image

packages/ai/test/exports.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,7 @@ describe("public exports", () => {
5353
expect(CloudflareWorkersAI.configure).toBeFunction()
5454
expect(CloudflareWorkersAI.configure({ accountId: "fixture", apiKey: "fixture" }).model).toBeFunction()
5555
expect(OpenRouter.model).toBeFunction()
56-
expect(OpenRouter.provider.model).toBe(OpenRouter.model)
5756
expect(XAI.model).toBeFunction()
58-
expect(XAI.provider.model).toBe(XAI.model)
5957
expect(XAI.provider.responses).toBe(XAI.responses)
6058
expect(XAI.provider.chat).toBe(XAI.chat)
6159
expect(XAI.configure({ apiKey: "fixture" }).responses("grok-4.3").route.id).toBe("openai-responses")

packages/ai/test/provider-package.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ describe("provider package entrypoints", () => {
2121
import("@opencode-ai/ai/providers/google-vertex/chat"),
2222
import("@opencode-ai/ai/providers/google-vertex/responses"),
2323
import("@opencode-ai/ai/providers/google-vertex/messages"),
24+
import("@opencode-ai/ai/providers/openrouter"),
25+
import("@opencode-ai/ai/providers/xai"),
2426
])
2527

2628
for (const module of modules) expect(module.model).toBeFunction()
@@ -29,6 +31,35 @@ describe("provider package entrypoints", () => {
2931
expect(modules[12].model).toBe(modules[13].model)
3032
})
3133

34+
test("maps OpenRouter and xAI package settings onto executable models", async () => {
35+
const OpenRouter = await import("@opencode-ai/ai/providers/openrouter")
36+
const XAI = await import("@opencode-ai/ai/providers/xai")
37+
const settings = {
38+
apiKey: "fixture",
39+
baseURL: "https://provider.example.test/v1",
40+
headers: { "x-application": "opencode" },
41+
body: { service_tier: "priority" },
42+
limits: { context: 200_000, output: 64_000 },
43+
}
44+
const openrouter = OpenRouter.model("anthropic/claude-sonnet-4", {
45+
...settings,
46+
providerOptions: { openrouter: { usage: true } },
47+
})
48+
const xai = XAI.model("grok-4", {
49+
...settings,
50+
providerOptions: { openai: { reasoningEffort: "high" } },
51+
})
52+
53+
for (const selected of [openrouter, xai]) {
54+
expect(selected.route.endpoint.baseURL).toBe(settings.baseURL)
55+
expect(selected.route.defaults.headers).toEqual(settings.headers)
56+
expect(selected.route.defaults.http?.body).toEqual(settings.body)
57+
expect(selected.route.defaults.limits).toEqual(settings.limits)
58+
}
59+
expect(openrouter.route.defaults.providerOptions).toEqual({ openrouter: { usage: true } })
60+
expect(xai.route.defaults.providerOptions).toEqual({ openai: { reasoningEffort: "high", store: false } })
61+
})
62+
3263
test("maps package settings onto the executable model", () => {
3364
const selected = model("gpt-5", {
3465
apiKey: "fixture",

0 commit comments

Comments
 (0)