diff --git a/packages/core/src/aisdk-native.ts b/packages/core/src/aisdk-native.ts index c75d720ad1a..9bbbbd66a46 100644 --- a/packages/core/src/aisdk-native.ts +++ b/packages/core/src/aisdk-native.ts @@ -32,15 +32,12 @@ export function rewrite( 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>>> = { "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 = { diff --git a/packages/core/src/provider.ts b/packages/core/src/provider.ts index 4cdca99696f..11154e7701d 100644 --- a/packages/core/src/provider.ts +++ b/packages/core/src/provider.ts @@ -46,19 +46,25 @@ export type ProviderPackage = ProviderPackageDefinition const packages = new Map>() const builtins = new Map Promise>([ + ["@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 Promise>([ ["@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 Promise>([ ["@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 Promise>([ ], ]) +/** Native `@opencode/ai/providers/*` entrypoints bundled with Core and loadable by name from config. */ +export const builtinPackages: ReadonlySet = 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) diff --git a/packages/core/src/variant.ts b/packages/core/src/variant.ts index 98dc3953f9b..e02c5dbfdd1 100644 --- a/packages/core/src/variant.ts +++ b/packages/core/src/variant.ts @@ -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> = { "@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> = { "@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> = { [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, diff --git a/packages/core/test/aisdk-native.test.ts b/packages/core/test/aisdk-native.test.ts index 7cb78033fe5..444d3785838 100644 --- a/packages/core/test/aisdk-native.test.ts +++ b/packages/core/test/aisdk-native.test.ts @@ -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, modelID = "test-model") => { + const target: { package?: string; settings?: Record; headers?: Record } = { + 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", () => { diff --git a/packages/core/test/provider.test.ts b/packages/core/test/provider.test.ts index 83620393537..0bc9d748064 100644 --- a/packages/core/test/provider.test.ts +++ b/packages/core/test/provider.test.ts @@ -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() diff --git a/packages/core/test/variant.test.ts b/packages/core/test/variant.test.ts index 8f2367bc7ac..574bd063f39 100644 --- a/packages/core/test/variant.test.ts +++ b/packages/core/test/variant.test.ts @@ -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" } }, + ]) +}) diff --git a/services/www/src/docs/content/providers.mdx b/services/www/src/docs/content/providers.mdx index 61adfd493ee..692fdbcb233 100644 --- a/services/www/src/docs/content/providers.mdx +++ b/services/www/src/docs/content/providers.mdx @@ -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`