From fea96490e308421e2f79dfd432b5a00e7339d5c3 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 3 May 2026 13:11:45 -0400 Subject: [PATCH] fix(llm): expand compatible provider bridge --- .../src/provider/openai-compatible-family.ts | 5 ++ packages/llm/src/provider/xai.ts | 4 +- packages/llm/test/provider-resolver.test.ts | 25 ++++++++- packages/opencode/src/provider/llm-bridge.ts | 6 +++ .../opencode/test/provider/llm-bridge.test.ts | 52 ++++++++++++++----- 5 files changed, 77 insertions(+), 15 deletions(-) diff --git a/packages/llm/src/provider/openai-compatible-family.ts b/packages/llm/src/provider/openai-compatible-family.ts index 19435cd7fe..acbbb9a06d 100644 --- a/packages/llm/src/provider/openai-compatible-family.ts +++ b/packages/llm/src/provider/openai-compatible-family.ts @@ -11,7 +11,12 @@ export const families = { deepinfra: { provider: "deepinfra", baseURL: "https://api.deepinfra.com/v1/openai" }, deepseek: { provider: "deepseek", baseURL: "https://api.deepseek.com/v1" }, fireworks: { provider: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1" }, + groq: { provider: "groq", baseURL: "https://api.groq.com/openai/v1" }, + mistral: { provider: "mistral", baseURL: "https://api.mistral.ai/v1" }, + openrouter: { provider: "openrouter", baseURL: "https://openrouter.ai/api/v1" }, + perplexity: { provider: "perplexity", baseURL: "https://api.perplexity.ai" }, togetherai: { provider: "togetherai", baseURL: "https://api.together.xyz/v1" }, + venice: { provider: "venice", baseURL: "https://api.venice.ai/api/v1" }, } as const satisfies Record export const byProvider: Record = Object.fromEntries( diff --git a/packages/llm/src/provider/xai.ts b/packages/llm/src/provider/xai.ts index 011a153cd6..268f59d845 100644 --- a/packages/llm/src/provider/xai.ts +++ b/packages/llm/src/provider/xai.ts @@ -1,5 +1,7 @@ import { ProviderResolver } from "../provider-resolver" -export const resolver = ProviderResolver.fixed("xai", "openai-responses") +export const resolver = ProviderResolver.fixed("xai", "openai-compatible-chat", { + baseURL: "https://api.x.ai/v1", +}) export * as XAI from "./xai" diff --git a/packages/llm/test/provider-resolver.test.ts b/packages/llm/test/provider-resolver.test.ts index 17a66dd887..a23301fe4e 100644 --- a/packages/llm/test/provider-resolver.test.ts +++ b/packages/llm/test/provider-resolver.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { Azure, GitHubCopilot, OpenAI, OpenAICompatibleFamily, ProviderResolver } from "../src" +import { Azure, GitHubCopilot, OpenAI, OpenAICompatibleFamily, ProviderResolver, XAI } from "../src" describe("provider resolver", () => { test("fixed providers resolve protocol and auth defaults", () => { @@ -30,6 +30,29 @@ describe("provider resolver", () => { baseURL: "https://api.together.xyz/v1", auth: "key", }) + expect(OpenAICompatibleFamily.resolver.resolve(ProviderResolver.input("llama", "groq", {}))).toMatchObject({ + provider: "groq", + protocol: "openai-compatible-chat", + baseURL: "https://api.groq.com/openai/v1", + }) + expect(OpenAICompatibleFamily.resolver.resolve(ProviderResolver.input("sonar", "perplexity", {}))).toMatchObject({ + provider: "perplexity", + protocol: "openai-compatible-chat", + baseURL: "https://api.perplexity.ai", + }) + expect(OpenAICompatibleFamily.resolver.resolve(ProviderResolver.input("gpt-5", "openrouter", {}))).toMatchObject({ + provider: "openrouter", + protocol: "openai-compatible-chat", + baseURL: "https://openrouter.ai/api/v1", + }) + }) + + test("xAI resolves to its OpenAI-compatible chat endpoint", () => { + expect(XAI.resolver.resolve(ProviderResolver.input("grok-4", "xai", {}))).toMatchObject({ + provider: "xai", + protocol: "openai-compatible-chat", + baseURL: "https://api.x.ai/v1", + }) }) test("Azure resolves resource URLs and API-version query params", () => { diff --git a/packages/opencode/src/provider/llm-bridge.ts b/packages/opencode/src/provider/llm-bridge.ts index 8a8cefd96a..13eac889c6 100644 --- a/packages/opencode/src/provider/llm-bridge.ts +++ b/packages/opencode/src/provider/llm-bridge.ts @@ -31,14 +31,20 @@ const PROVIDERS: Record = { "@ai-sdk/azure": Azure.resolver, "@ai-sdk/baseten": OpenAICompatibleFamily.resolver, "@ai-sdk/cerebras": OpenAICompatibleFamily.resolver, + "@ai-sdk/deepseek": OpenAICompatibleFamily.resolver, "@ai-sdk/deepinfra": OpenAICompatibleFamily.resolver, "@ai-sdk/fireworks": OpenAICompatibleFamily.resolver, + "@ai-sdk/groq": OpenAICompatibleFamily.resolver, "@ai-sdk/github-copilot": GitHubCopilot.resolver, "@ai-sdk/google": Google.resolver, + "@ai-sdk/mistral": OpenAICompatibleFamily.resolver, "@ai-sdk/openai": OpenAI.resolver, "@ai-sdk/openai-compatible": OpenAICompatibleFamily.resolver, + "@ai-sdk/perplexity": OpenAICompatibleFamily.resolver, "@ai-sdk/togetherai": OpenAICompatibleFamily.resolver, "@ai-sdk/xai": XAI.resolver, + "@openrouter/ai-sdk-provider": OpenAICompatibleFamily.resolver, + "venice-ai-sdk-provider": OpenAICompatibleFamily.resolver, } const REASONING_EFFORTS = new Set(ReasoningEfforts) diff --git a/packages/opencode/test/provider/llm-bridge.test.ts b/packages/opencode/test/provider/llm-bridge.test.ts index 8df0af8a26..68bd34ff3b 100644 --- a/packages/opencode/test/provider/llm-bridge.test.ts +++ b/packages/opencode/test/provider/llm-bridge.test.ts @@ -87,22 +87,46 @@ describe("ProviderLLMBridge", () => { }) test("maps known OpenAI-compatible provider families", () => { + const cases = [ + ["togetherai", "@ai-sdk/togetherai", "https://api.together.xyz/v1"], + ["openrouter", "@openrouter/ai-sdk-provider", "https://openrouter.ai/api/v1"], + ["groq", "@ai-sdk/groq", "https://api.groq.com/openai/v1"], + ["mistral", "@ai-sdk/mistral", "https://api.mistral.ai/v1"], + ["perplexity", "@ai-sdk/perplexity", "https://api.perplexity.ai"], + ["venice", "venice-ai-sdk-provider", "https://api.venice.ai/api/v1"], + ] as const + + for (const [providerID, npm, baseURL] of cases) { + const ref = ProviderLLMBridge.toModelRef({ + provider: provider({ id: ProviderID.make(providerID), options: { apiKey: `${providerID}-key` } }), + model: model({ + id: "llama", + apiID: providerID === "togetherai" ? "meta-llama/Llama-3.3-70B-Instruct-Turbo" : "model-1", + providerID, + npm, + }), + }) + + expect(ref).toMatchObject({ + provider: providerID, + protocol: "openai-compatible-chat", + baseURL, + apiKey: `${providerID}-key`, + }) + } + }) + + test("maps xAI to OpenAI-compatible Chat", () => { const ref = ProviderLLMBridge.toModelRef({ - provider: provider({ id: ProviderID.make("togetherai"), options: { apiKey: "together-key" } }), - model: model({ - id: "llama", - apiID: "meta-llama/Llama-3.3-70B-Instruct-Turbo", - providerID: "togetherai", - npm: "@ai-sdk/togetherai", - }), + provider: provider({ id: ProviderID.make("xai"), key: "xai-key" }), + model: model({ id: "grok-4", providerID: "xai", npm: "@ai-sdk/xai" }), }) expect(ref).toMatchObject({ - id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", - provider: "togetherai", + provider: "xai", protocol: "openai-compatible-chat", - baseURL: "https://api.together.xyz/v1", - apiKey: "together-key", + baseURL: "https://api.x.ai/v1", + apiKey: "xai-key", }) }) @@ -202,7 +226,9 @@ describe("ProviderLLMBridge", () => { test("leaves undecided provider packages unmapped", () => { const unsupported = [ - ["mistral", "mistral-large", "@ai-sdk/mistral"], + ["cohere", "command-a", "@ai-sdk/cohere"], + ["google-vertex", "gemini-2.5-flash", "@ai-sdk/google-vertex"], + ["gateway", "openai/gpt-5", "@ai-sdk/gateway"], ] as const expect( @@ -212,6 +238,6 @@ describe("ProviderLLMBridge", () => { model: model({ id: modelID, providerID, npm }), }), ), - ).toEqual([undefined, undefined]) + ).toEqual([undefined, undefined, undefined]) }) })