mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 03:56:55 +03:00
fix(core): load and spell reasoning for every native provider package (#55789)
This commit is contained in:
committed by
GitHub
Unverified
parent
fe87944b6a
commit
412b2b1d85
@@ -32,15 +32,12 @@ export function rewrite<ID extends string>(
|
||||
const replacement = resolve(input.specifier, { ...input, settings }) ?? plain
|
||||
const translated = options(replacement, input.modelID, settings)
|
||||
if (target.package !== undefined || replacement !== input.specifier) target.package = replacement
|
||||
target.settings =
|
||||
target.settings === undefined
|
||||
? undefined
|
||||
: {
|
||||
...translated.settings,
|
||||
...(!NATIVE.has(input.specifier) && replacement === "@opencode/ai/providers/openai-compatible"
|
||||
? { provider: input.canonical ?? input.providerID }
|
||||
: {}),
|
||||
}
|
||||
// Without `provider`, the native compatible package reports every AI SDK-mapped host as `openai-compatible`.
|
||||
const identity =
|
||||
replacement === "@opencode/ai/providers/openai-compatible" && !Provider.builtinPackages.has(input.specifier)
|
||||
? { provider: input.canonical ?? input.providerID }
|
||||
: {}
|
||||
target.settings = target.settings === undefined ? undefined : { ...translated.settings, ...identity }
|
||||
target.headers = Provider.mergeHeaders(translated.headers, target.headers)
|
||||
target.body = Provider.mergeOverlay(translated.body, target.body)
|
||||
target.variants = target.variants?.map((variant) => {
|
||||
@@ -122,15 +119,6 @@ const HOSTS: Readonly<Record<string, Readonly<Record<string, string>>>> = {
|
||||
"zhipuai-coding-plan": protocols("zai-coding-plan"),
|
||||
}
|
||||
|
||||
const NATIVE = new Set([
|
||||
...Object.values(PACKAGES),
|
||||
...Object.values(HOSTS).flatMap((host) => Object.values(host)),
|
||||
"@opencode/ai/providers/azure/chat",
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle",
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle/chat",
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle/responses",
|
||||
])
|
||||
|
||||
export function native(npm: string, context: Context & { readonly settings?: Provider.Settings }): string | undefined {
|
||||
const host = HOSTS[context.providerID]?.[npm]
|
||||
if (host) return host
|
||||
@@ -142,8 +130,10 @@ export function native(npm: string, context: Context & { readonly settings?: Pro
|
||||
const azureChat = (context: Context & { readonly settings?: Provider.Settings }) =>
|
||||
context.shape === "completions" || context.settings?.useCompletionUrls === true
|
||||
|
||||
// Mantle serves Claude only through Anthropic Messages and GPT-OSS only through Chat.
|
||||
const mantle = (modelID: string | undefined) => {
|
||||
if (modelID === undefined) return "@opencode/ai/providers/amazon-bedrock/mantle"
|
||||
if (modelID.includes("anthropic.")) return "@opencode/ai/providers/amazon-bedrock/mantle/messages"
|
||||
return `@opencode/ai/providers/amazon-bedrock/mantle/${modelID.includes("gpt-oss") ? "chat" : "responses"}`
|
||||
}
|
||||
|
||||
@@ -151,9 +141,14 @@ function resolve(specifier: string, context: Context & { readonly settings?: Pro
|
||||
const npm = Provider.packageName(specifier)
|
||||
if (Provider.isAISDK(specifier) || npm in PACKAGES || npm in (HOSTS[context.providerID] ?? {}))
|
||||
return native(npm, context)
|
||||
if (npm === "@opencode/ai/providers/amazon-bedrock/mantle") return mantle(context.modelID)
|
||||
if (npm === "@opencode/ai/providers/azure/responses" && azureChat(context)) return "@opencode/ai/providers/azure/chat"
|
||||
return NATIVE.has(npm) ? npm : undefined
|
||||
if (npm === "@opencode/ai/providers/amazon-bedrock/mantle" || npm === "@opencode/ai/providers/amazon-bedrock-mantle")
|
||||
return mantle(context.modelID)
|
||||
if (
|
||||
(npm === "@opencode/ai/providers/azure" || npm === "@opencode/ai/providers/azure/responses") &&
|
||||
azureChat(context)
|
||||
)
|
||||
return "@opencode/ai/providers/azure/chat"
|
||||
return Provider.builtinPackages.has(npm) ? npm : undefined
|
||||
}
|
||||
|
||||
type Overlay = {
|
||||
|
||||
@@ -46,19 +46,25 @@ export type ProviderPackage = ProviderPackageDefinition
|
||||
|
||||
const packages = new Map<string, Promise<unknown>>()
|
||||
const builtins = new Map<string, () => Promise<unknown>>([
|
||||
["@opencode/ai/providers/alibaba", () => import("@opencode/ai/providers/alibaba")],
|
||||
["@opencode/ai/providers/alibaba/chat", () => import("@opencode/ai/providers/alibaba/chat")],
|
||||
["@opencode/ai/providers/alibaba/messages", () => import("@opencode/ai/providers/alibaba/messages")],
|
||||
["@opencode/ai/providers/alibaba/responses", () => import("@opencode/ai/providers/alibaba/responses")],
|
||||
["@opencode/ai/providers/amazon-bedrock", () => import("@opencode/ai/providers/amazon-bedrock")],
|
||||
["@opencode/ai/providers/amazon-bedrock/mantle", () => import("@opencode/ai/providers/amazon-bedrock/mantle")],
|
||||
[
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle/chat",
|
||||
() => import("@opencode/ai/providers/amazon-bedrock/mantle/chat"),
|
||||
],
|
||||
[
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle/messages",
|
||||
() => import("@opencode/ai/providers/amazon-bedrock/mantle/messages"),
|
||||
],
|
||||
[
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle/responses",
|
||||
() => import("@opencode/ai/providers/amazon-bedrock/mantle/responses"),
|
||||
],
|
||||
["@opencode/ai/providers/alibaba/chat", () => import("@opencode/ai/providers/alibaba/chat")],
|
||||
["@opencode/ai/providers/alibaba/messages", () => import("@opencode/ai/providers/alibaba/messages")],
|
||||
["@opencode/ai/providers/alibaba/responses", () => import("@opencode/ai/providers/alibaba/responses")],
|
||||
["@opencode/ai/providers/amazon-bedrock-mantle", () => import("@opencode/ai/providers/amazon-bedrock-mantle")],
|
||||
["@opencode/ai/providers/anthropic", () => import("@opencode/ai/providers/anthropic")],
|
||||
["@opencode/ai/providers/anthropic-compatible", () => import("@opencode/ai/providers/anthropic-compatible")],
|
||||
["@opencode/ai/providers/azure", () => import("@opencode/ai/providers/azure")],
|
||||
@@ -77,23 +83,34 @@ const builtins = new Map<string, () => Promise<unknown>>([
|
||||
["@opencode/ai/providers/google", () => import("@opencode/ai/providers/google")],
|
||||
["@opencode/ai/providers/google/interactions", () => import("@opencode/ai/providers/google/interactions")],
|
||||
["@opencode/ai/providers/google-vertex", () => import("@opencode/ai/providers/google-vertex")],
|
||||
["@opencode/ai/providers/google-vertex/chat", () => import("@opencode/ai/providers/google-vertex/chat")],
|
||||
["@opencode/ai/providers/google-vertex/gemini", () => import("@opencode/ai/providers/google-vertex/gemini")],
|
||||
[
|
||||
"@opencode/ai/providers/google-vertex/interactions",
|
||||
() => import("@opencode/ai/providers/google-vertex/interactions"),
|
||||
],
|
||||
["@opencode/ai/providers/google-vertex/chat", () => import("@opencode/ai/providers/google-vertex/chat")],
|
||||
["@opencode/ai/providers/google-vertex/responses", () => import("@opencode/ai/providers/google-vertex/responses")],
|
||||
["@opencode/ai/providers/google-vertex/messages", () => import("@opencode/ai/providers/google-vertex/messages")],
|
||||
["@opencode/ai/providers/google-vertex/mistral", () => import("@opencode/ai/providers/google-vertex/mistral")],
|
||||
["@opencode/ai/providers/google-vertex/responses", () => import("@opencode/ai/providers/google-vertex/responses")],
|
||||
["@opencode/ai/providers/google-vertex-chat", () => import("@opencode/ai/providers/google-vertex-chat")],
|
||||
[
|
||||
"@opencode/ai/providers/google-vertex-interactions",
|
||||
() => import("@opencode/ai/providers/google-vertex-interactions"),
|
||||
],
|
||||
["@opencode/ai/providers/google-vertex-messages", () => import("@opencode/ai/providers/google-vertex-messages")],
|
||||
["@opencode/ai/providers/google-vertex-mistral", () => import("@opencode/ai/providers/google-vertex-mistral")],
|
||||
["@opencode/ai/providers/google-vertex-responses", () => import("@opencode/ai/providers/google-vertex-responses")],
|
||||
["@opencode/ai/providers/groq", () => import("@opencode/ai/providers/groq")],
|
||||
["@opencode/ai/providers/meta", () => import("@opencode/ai/providers/meta")],
|
||||
["@opencode/ai/providers/meta/chat", () => import("@opencode/ai/providers/meta/chat")],
|
||||
["@opencode/ai/providers/meta/messages", () => import("@opencode/ai/providers/meta/messages")],
|
||||
["@opencode/ai/providers/meta/responses", () => import("@opencode/ai/providers/meta/responses")],
|
||||
["@opencode/ai/providers/minimax", () => import("@opencode/ai/providers/minimax")],
|
||||
["@opencode/ai/providers/minimax/chat", () => import("@opencode/ai/providers/minimax/chat")],
|
||||
["@opencode/ai/providers/minimax/messages", () => import("@opencode/ai/providers/minimax/messages")],
|
||||
["@opencode/ai/providers/minimax/responses", () => import("@opencode/ai/providers/minimax/responses")],
|
||||
["@opencode/ai/providers/mistral", () => import("@opencode/ai/providers/mistral")],
|
||||
["@opencode/ai/providers/moonshot", () => import("@opencode/ai/providers/moonshot")],
|
||||
["@opencode/ai/providers/moonshot/chat", () => import("@opencode/ai/providers/moonshot/chat")],
|
||||
["@opencode/ai/providers/moonshot/messages", () => import("@opencode/ai/providers/moonshot/messages")],
|
||||
["@opencode/ai/providers/moonshot/responses", () => import("@opencode/ai/providers/moonshot/responses")],
|
||||
@@ -113,8 +130,19 @@ const builtins = new Map<string, () => Promise<unknown>>([
|
||||
["@opencode/ai/providers/togetherai", () => import("@opencode/ai/providers/togetherai")],
|
||||
["@opencode/ai/providers/venice", () => import("@opencode/ai/providers/venice")],
|
||||
["@opencode/ai/providers/vercel-ai-gateway", () => import("@opencode/ai/providers/vercel-ai-gateway")],
|
||||
["@opencode/ai/providers/vercel-ai-gateway/chat", () => import("@opencode/ai/providers/vercel-ai-gateway/chat")],
|
||||
[
|
||||
"@opencode/ai/providers/vercel-ai-gateway/messages",
|
||||
() => import("@opencode/ai/providers/vercel-ai-gateway/messages"),
|
||||
],
|
||||
[
|
||||
"@opencode/ai/providers/vercel-ai-gateway/responses",
|
||||
() => import("@opencode/ai/providers/vercel-ai-gateway/responses"),
|
||||
],
|
||||
["@opencode/ai/providers/xai", () => import("@opencode/ai/providers/xai")],
|
||||
["@opencode/ai/providers/zai", () => import("@opencode/ai/providers/zai")],
|
||||
["@opencode/ai/providers/zai/chat", () => import("@opencode/ai/providers/zai/chat")],
|
||||
["@opencode/ai/providers/zai-coding-plan", () => import("@opencode/ai/providers/zai-coding-plan")],
|
||||
["@opencode/ai/providers/zai-coding-plan/chat", () => import("@opencode/ai/providers/zai-coding-plan/chat")],
|
||||
["@opencode/ai/providers/zai-coding-plan/messages", () => import("@opencode/ai/providers/zai-coding-plan/messages")],
|
||||
[
|
||||
@@ -123,6 +151,9 @@ const builtins = new Map<string, () => Promise<unknown>>([
|
||||
],
|
||||
])
|
||||
|
||||
/** Native `@opencode/ai/providers/*` entrypoints bundled with Core and loadable by name from config. */
|
||||
export const builtinPackages: ReadonlySet<string> = new Set(builtins.keys())
|
||||
|
||||
export const loadPackage = Effect.fn("Provider.loadPackage")(function* (input: string, npm?: Npm.Interface) {
|
||||
const specifier = packageName(input)
|
||||
const builtin = builtins.get(specifier)
|
||||
|
||||
@@ -99,11 +99,18 @@ const responsesEffort = (effort: string): Overlay => ({
|
||||
settings: { reasoningEffort: effort, reasoningSummary: "auto", include: ENCRYPTED_REASONING },
|
||||
})
|
||||
|
||||
const xaiResponses: Protocol = (_, support) => {
|
||||
const openResponses: Protocol = (_, support) => {
|
||||
if (support.type !== "effort") return []
|
||||
return efforts(support.values ?? EFFORTS, responsesEffort)
|
||||
}
|
||||
|
||||
const googleInteractions: Protocol = (_, support) => {
|
||||
if (support.type !== "effort") return []
|
||||
return efforts(support.values ?? EFFORTS, (effort) => ({
|
||||
settings: { thinkingLevel: effort, thinkingSummaries: "auto" },
|
||||
}))
|
||||
}
|
||||
|
||||
const cloudflareAIGateway: Protocol = (model, support) => {
|
||||
const id = modelID(model)
|
||||
if (id.startsWith("openai/")) return openaiResponses(model, support)
|
||||
@@ -466,8 +473,17 @@ const cohere: Protocol = (model, support) => {
|
||||
const vercelGateway: Protocol = (model, support) => {
|
||||
const id = modelID(model)
|
||||
if (id.startsWith("openai/gpt-")) return openaiResponses(model, support)
|
||||
if (id.startsWith("spacexai/grok-")) return xaiResponses(model, support)
|
||||
if (id.startsWith("anthropic/")) return anthropicMessages(model, support)
|
||||
if (id.startsWith("spacexai/grok-")) return openResponses(model, support)
|
||||
return vercelMessages(model, support)
|
||||
}
|
||||
|
||||
const vercelResponses: Protocol = (model, support) =>
|
||||
modelID(model).startsWith("openai/gpt-") ? openaiResponses(model, support) : openResponses(model, support)
|
||||
|
||||
const vercelMessages: Protocol = (model, support) =>
|
||||
modelID(model).startsWith("anthropic/") ? anthropicMessages(model, support) : vercelChat(model, support)
|
||||
|
||||
const vercelChat: Protocol = (model, support) => {
|
||||
switch (support.type) {
|
||||
case "effort":
|
||||
return openaiChat(model, support)
|
||||
@@ -478,7 +494,7 @@ const vercelGateway: Protocol = (model, support) => {
|
||||
model,
|
||||
support,
|
||||
(tokens) => ({ settings: { thinking: { type: "enabled", budgetTokens: tokens } } }),
|
||||
id.startsWith("alibaba/") ? ALIBABA_THINKING_BUDGET_MAX : model.limit.output,
|
||||
modelID(model).startsWith("alibaba/") ? ALIBABA_THINKING_BUDGET_MAX : model.limit.output,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -576,18 +592,32 @@ const gitlab: Protocol = (model, support) => {
|
||||
|
||||
const PROTOCOLS: Readonly<Record<string, Protocol>> = {
|
||||
"@opencode/ai/providers/openai": openaiResponses,
|
||||
"@opencode/ai/providers/openai/responses": openaiResponses,
|
||||
"@opencode/ai/providers/azure": openaiResponses,
|
||||
"@opencode/ai/providers/azure/responses": openaiResponses,
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle/chat": openaiResponses,
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle": openaiResponses,
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle/responses": openaiResponses,
|
||||
"@opencode/ai/providers/amazon-bedrock-mantle": openaiResponses,
|
||||
"@opencode/ai/providers/alibaba/responses": openaiResponses,
|
||||
"@opencode/ai/providers/meta": openaiResponses,
|
||||
"@opencode/ai/providers/meta/responses": openaiResponses,
|
||||
"@opencode/ai/providers/minimax/responses": openaiResponses,
|
||||
"@opencode/ai/providers/moonshot/responses": openaiResponses,
|
||||
"@opencode/ai/providers/zai-coding-plan/responses": openaiResponses,
|
||||
|
||||
"@opencode/ai/providers/openai-compatible/responses": openResponses,
|
||||
"@opencode/ai/providers/openai-compatible-responses": openResponses,
|
||||
"@opencode/ai/providers/google-vertex/responses": openResponses,
|
||||
"@opencode/ai/providers/google-vertex-responses": openResponses,
|
||||
"@opencode/ai/providers/xai": openResponses,
|
||||
|
||||
"@opencode/ai/providers/openai-compatible": openaiCompatible,
|
||||
"@opencode/ai/providers/openai/chat": openaiChat,
|
||||
"@opencode/ai/providers/azure/chat": openaiChat,
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle/chat": openaiChat,
|
||||
"@opencode/ai/providers/google-vertex/chat": vertexChat,
|
||||
"@opencode/ai/providers/google-vertex-chat": vertexChat,
|
||||
"@opencode/ai/providers/alibaba": alibabaChat,
|
||||
"@opencode/ai/providers/alibaba/chat": alibabaChat,
|
||||
"@opencode/ai/providers/baseten": basetenChat,
|
||||
"@opencode/ai/providers/cerebras": openaiChat,
|
||||
@@ -601,24 +631,35 @@ const PROTOCOLS: Readonly<Record<string, Protocol>> = {
|
||||
"@opencode/ai/providers/meta/chat": openaiChat,
|
||||
"@opencode/ai/providers/minimax/chat": openaiChat,
|
||||
"@opencode/ai/providers/mistral": openaiChat,
|
||||
"@opencode/ai/providers/google-vertex/mistral": openaiChat,
|
||||
"@opencode/ai/providers/google-vertex-mistral": openaiChat,
|
||||
"@opencode/ai/providers/moonshot": moonshotChat,
|
||||
"@opencode/ai/providers/moonshot/chat": moonshotChat,
|
||||
"@opencode/ai/providers/togetherai": openaiChat,
|
||||
"@opencode/ai/providers/venice": veniceChat,
|
||||
"@opencode/ai/providers/xai": xaiResponses,
|
||||
"@opencode/ai/providers/zai": zaiChat,
|
||||
"@opencode/ai/providers/zai/chat": zaiChat,
|
||||
"@opencode/ai/providers/zai-coding-plan": zaiChat,
|
||||
"@opencode/ai/providers/zai-coding-plan/chat": zaiChat,
|
||||
|
||||
"@opencode/ai/providers/anthropic": anthropicMessages,
|
||||
"@opencode/ai/providers/anthropic-compatible": anthropicMessages,
|
||||
"@opencode/ai/providers/amazon-bedrock/mantle/messages": anthropicMessages,
|
||||
"@opencode/ai/providers/google-vertex/messages": anthropicMessages,
|
||||
"@opencode/ai/providers/google-vertex/mistral": openaiChat,
|
||||
"@opencode/ai/providers/google-vertex-messages": anthropicMessages,
|
||||
"@opencode/ai/providers/alibaba/messages": alibabaMessages,
|
||||
"@opencode/ai/providers/meta/messages": anthropicMessages,
|
||||
"@opencode/ai/providers/minimax": minimaxMessages,
|
||||
"@opencode/ai/providers/minimax/messages": minimaxMessages,
|
||||
"@opencode/ai/providers/moonshot/messages": moonshotMessages,
|
||||
"@opencode/ai/providers/zai-coding-plan/messages": zaiMessages,
|
||||
|
||||
"@opencode/ai/providers/google": gemini,
|
||||
"@opencode/ai/providers/google-vertex": gemini,
|
||||
"@opencode/ai/providers/google-vertex/gemini": gemini,
|
||||
"@opencode/ai/providers/google/interactions": googleInteractions,
|
||||
"@opencode/ai/providers/google-vertex/interactions": googleInteractions,
|
||||
"@opencode/ai/providers/google-vertex-interactions": googleInteractions,
|
||||
|
||||
"@opencode/ai/providers/amazon-bedrock": bedrockConverse,
|
||||
"@opencode/ai/providers/cohere": cohere,
|
||||
@@ -627,6 +668,9 @@ const PROTOCOLS: Readonly<Record<string, Protocol>> = {
|
||||
[Provider.aisdk("venice-ai-sdk-provider")]: veniceChat,
|
||||
"@opencode/ai/providers/cloudflare-ai-gateway": cloudflareAIGateway,
|
||||
"@opencode/ai/providers/vercel-ai-gateway": vercelGateway,
|
||||
"@opencode/ai/providers/vercel-ai-gateway/chat": vercelChat,
|
||||
"@opencode/ai/providers/vercel-ai-gateway/messages": vercelMessages,
|
||||
"@opencode/ai/providers/vercel-ai-gateway/responses": vercelResponses,
|
||||
[Provider.aisdk("@ai-sdk/gateway")]: vercelGateway,
|
||||
[Provider.aisdk("@jerome-benoit/sap-ai-provider-v2")]: sapAICore,
|
||||
[Provider.aisdk("gitlab-ai-provider")]: gitlab,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { AISDKNative } from "@opencode/core/aisdk-native"
|
||||
import { Provider } from "@opencode/core/provider"
|
||||
|
||||
function map(
|
||||
packageName: string,
|
||||
@@ -225,6 +226,35 @@ describe("AISDKNative", () => {
|
||||
package: "@opencode/ai/providers/amazon-bedrock/mantle/chat",
|
||||
settings: { region: "us-east-1" },
|
||||
})
|
||||
expect(
|
||||
map("@ai-sdk/amazon-bedrock/mantle", { region: "us-east-1", profile: "sso" }, "anthropic.claude-opus-4-8"),
|
||||
).toEqual({
|
||||
package: "@opencode/ai/providers/amazon-bedrock/mantle/messages",
|
||||
settings: { region: "us-east-1", profile: "sso" },
|
||||
})
|
||||
})
|
||||
|
||||
test("translates settings for every bundled native package named in config", () => {
|
||||
const rewrite = (specifier: string, settings: Record<string, unknown>, modelID = "test-model") => {
|
||||
const target: { package?: string; settings?: Record<string, unknown>; headers?: Record<string, string> } = {
|
||||
package: specifier,
|
||||
settings,
|
||||
}
|
||||
AISDKNative.rewrite(target, { specifier, providerID: "test-provider", modelID })
|
||||
return target
|
||||
}
|
||||
|
||||
for (const specifier of Provider.builtinPackages)
|
||||
expect(rewrite(specifier, { headers: { "x-test": "value" } }).headers).toEqual({ "x-test": "value" })
|
||||
expect(
|
||||
rewrite("@opencode/ai/providers/amazon-bedrock/mantle", { bearerToken: "token" }, "anthropic.claude-opus-4-8"),
|
||||
).toMatchObject({
|
||||
package: "@opencode/ai/providers/amazon-bedrock/mantle/messages",
|
||||
settings: { apiKey: "token" },
|
||||
})
|
||||
expect(rewrite("@opencode/ai/providers/azure", { useCompletionUrls: true }).package).toBe(
|
||||
"@opencode/ai/providers/azure/chat",
|
||||
)
|
||||
})
|
||||
|
||||
test("maps Azure deployments and settings to native routes", () => {
|
||||
|
||||
@@ -1,31 +1,22 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import path from "node:path"
|
||||
import { Effect } from "effect"
|
||||
import { Provider } from "@opencode/core/provider"
|
||||
|
||||
describe("Provider", () => {
|
||||
test("loads bundled native provider entrypoints", async () => {
|
||||
const packages = [
|
||||
"@opencode/ai/providers/baseten",
|
||||
"@opencode/ai/providers/cerebras",
|
||||
"@opencode/ai/providers/cloudflare-ai-gateway",
|
||||
"@opencode/ai/providers/cloudflare-workers-ai",
|
||||
"@opencode/ai/providers/cohere",
|
||||
"@opencode/ai/providers/cohere/chat",
|
||||
"@opencode/ai/providers/deepinfra",
|
||||
"@opencode/ai/providers/deepseek",
|
||||
"@opencode/ai/providers/fireworks",
|
||||
"@opencode/ai/providers/google-vertex",
|
||||
"@opencode/ai/providers/google-vertex/gemini",
|
||||
"@opencode/ai/providers/google-vertex/interactions",
|
||||
"@opencode/ai/providers/google-vertex/chat",
|
||||
"@opencode/ai/providers/google-vertex/responses",
|
||||
"@opencode/ai/providers/google-vertex/messages",
|
||||
"@opencode/ai/providers/groq",
|
||||
"@opencode/ai/providers/mistral",
|
||||
"@opencode/ai/providers/togetherai",
|
||||
"@opencode/ai/providers/vercel-ai-gateway",
|
||||
]
|
||||
test("bundles every native provider entrypoint that exports model()", async () => {
|
||||
const providers = path.join(import.meta.dir, "../../ai/src/providers")
|
||||
const entrypoints = await Promise.all(
|
||||
Array.from(new Bun.Glob("**/*.ts").scanSync(providers)).map(async (file) => {
|
||||
const specifier = `@opencode/ai/providers/${file.replace(/\.ts$/, "")}`
|
||||
const module = await import(specifier)
|
||||
return typeof module.model === "function" ? specifier : undefined
|
||||
}),
|
||||
)
|
||||
const packages = entrypoints.filter((specifier) => specifier !== undefined).toSorted()
|
||||
|
||||
expect(packages.filter((specifier) => !Provider.builtinPackages.has(specifier))).toEqual([])
|
||||
expect(Array.from(Provider.builtinPackages).toSorted()).toEqual(packages)
|
||||
for (const specifier of packages) {
|
||||
const loaded = await Effect.runPromise(Provider.loadPackage(specifier))
|
||||
expect(loaded.model).toBeFunction()
|
||||
|
||||
@@ -164,6 +164,17 @@ test("spells Vercel AI Gateway variants for their selected routes", () => {
|
||||
{ id: "high", settings: { thinking: { type: "enabled", budgetTokens: 19_456 } } },
|
||||
{ id: "max", settings: { thinking: { type: "enabled", budgetTokens: 38_911 } } },
|
||||
])
|
||||
expect(resolve(model(`${pkg}/chat`, "anthropic/claude-sonnet-4.6"), [{ type: "effort", values: ["high"] }])).toEqual([
|
||||
{ id: "high", settings: { reasoningEffort: "high" } },
|
||||
])
|
||||
expect(
|
||||
resolve(model(`${pkg}/messages`, "anthropic/claude-sonnet-4.6"), [{ type: "effort", values: ["high"] }]),
|
||||
).toEqual([{ id: "high", settings: { effort: "high", thinking: { type: "adaptive", display: "summarized" } } }])
|
||||
expect(resolve(model(`${pkg}/responses`, "spacexai/grok-4.7"), [{ type: "effort" }]).map((item) => item.id)).toEqual([
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
])
|
||||
})
|
||||
|
||||
test("spells xAI Responses variants with catalog effort levels", () => {
|
||||
@@ -496,3 +507,40 @@ test("spells GitLab Duo variants for gitlab-ai-provider routes", () => {
|
||||
expect(resolve(gitlab("duo-chat-gpt-5-4"), [])).toEqual([])
|
||||
expect(resolve(gitlab("duo-workflow-opus-5-5"), [{ type: "effort" }])).toEqual([])
|
||||
})
|
||||
|
||||
test("spells reasoning variants for every bundled native package", () => {
|
||||
const supports: Variant.Support[] = [{ type: "toggle" }, { type: "effort" }, { type: "budget_tokens" }]
|
||||
const missing = Array.from(Provider.builtinPackages).filter(
|
||||
(packageName) => Variant.resolve(model(packageName, "test-model", 32_000), supports).length === 0,
|
||||
)
|
||||
expect(missing).toEqual([])
|
||||
})
|
||||
|
||||
test("spells Mantle variants for the API each model is routed through", () => {
|
||||
expect(
|
||||
resolve(model("@opencode/ai/providers/amazon-bedrock/mantle/messages", "anthropic.claude-opus-4-8"), [
|
||||
{ type: "effort", values: ["low", "max"] },
|
||||
]),
|
||||
).toEqual([
|
||||
{ id: "low", settings: { effort: "low", thinking: { type: "adaptive", display: "summarized" } } },
|
||||
{ id: "max", settings: { effort: "max", thinking: { type: "adaptive", display: "summarized" } } },
|
||||
])
|
||||
expect(
|
||||
resolve(model("@opencode/ai/providers/amazon-bedrock/mantle/chat", "openai.gpt-oss-120b"), [{ type: "effort" }]),
|
||||
).toEqual([
|
||||
{ id: "low", settings: { reasoningEffort: "low" } },
|
||||
{ id: "medium", settings: { reasoningEffort: "medium" } },
|
||||
{ id: "high", settings: { reasoningEffort: "high" } },
|
||||
])
|
||||
})
|
||||
|
||||
test("spells Google Interactions variants as thinking levels", () => {
|
||||
for (const packageName of [
|
||||
"@opencode/ai/providers/google/interactions",
|
||||
"@opencode/ai/providers/google-vertex/interactions",
|
||||
])
|
||||
expect(resolve(model(packageName, "gemini-3.5-pro"), [{ type: "effort", values: ["low", "high"] }])).toEqual([
|
||||
{ id: "low", settings: { thinkingLevel: "low", thinkingSummaries: "auto" } },
|
||||
{ id: "high", settings: { thinkingLevel: "high", thinkingSummaries: "auto" } },
|
||||
])
|
||||
})
|
||||
|
||||
@@ -218,6 +218,7 @@ Native package options include:
|
||||
- `@opencode/ai/providers/amazon-bedrock`
|
||||
- `@opencode/ai/providers/amazon-bedrock/mantle`
|
||||
- `@opencode/ai/providers/amazon-bedrock/mantle/chat`
|
||||
- `@opencode/ai/providers/amazon-bedrock/mantle/messages`
|
||||
- `@opencode/ai/providers/amazon-bedrock/mantle/responses`
|
||||
- `@opencode/ai/providers/openrouter`
|
||||
- `@opencode/ai/providers/xai`
|
||||
|
||||
Reference in New Issue
Block a user