Forráskód Böngészése

fix(ai): apply catalog settings to provider models (#39613)

Aiden Cline 2 hete
szülő
commit
906dc8f5b2

+ 19 - 1
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<RouteDefaultsInput, "providerOptions"> &
     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<Settings, OpenRouterProviderOptionsInput>["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)

+ 16 - 1
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<RouteDefaultsInput, "providerOptions"> &
     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<Settings, OpenAIProviderOptionsInput>["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

+ 0 - 2
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")

+ 31 - 0
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",