diff --git a/packages/llm/src/provider-resolver.ts b/packages/llm/src/provider-resolver.ts index 03c4bf66fc..809b1d7ef8 100644 --- a/packages/llm/src/provider-resolver.ts +++ b/packages/llm/src/provider-resolver.ts @@ -8,7 +8,7 @@ export interface ProviderResolution { readonly provider: ProviderIDType readonly protocol: Protocol readonly baseURL?: string - readonly auth?: ProviderAuth + readonly auth: ProviderAuth readonly capabilities?: CapabilitiesInput } @@ -26,10 +26,11 @@ export interface ProviderResolver { export const make = ( provider: string | ProviderIDType, protocol: Protocol, - options: Omit = {}, + options: Partial> = {}, ): ProviderResolution => ({ provider: ProviderID.make(provider), protocol, + auth: options.auth ?? "bearer", ...options, }) @@ -38,7 +39,7 @@ export const define = (input: ProviderResolver): ProviderResolver => input export const fixed = ( provider: string | ProviderIDType, protocol: Protocol, - options: Omit = {}, + options: Partial> = {}, ): ProviderResolver => { const resolution = make(provider, protocol, options) return define({ id: resolution.provider, resolve: () => resolution }) diff --git a/packages/llm/src/provider/openai-compatible-family.ts b/packages/llm/src/provider/openai-compatible-family.ts index 2923ac2d80..19435cd7fe 100644 --- a/packages/llm/src/provider/openai-compatible-family.ts +++ b/packages/llm/src/provider/openai-compatible-family.ts @@ -18,8 +18,15 @@ export const byProvider: Record = Object.fromEntries( Object.values(families).map((family) => [family.provider, family]), ) +const resolutions = Object.fromEntries( + Object.values(families).map((family) => [ + family.provider, + ProviderResolver.make(family.provider, "openai-compatible-chat", { baseURL: family.baseURL }), + ]), +) + export const resolve = (provider: string) => - ProviderResolver.make(provider, "openai-compatible-chat", { baseURL: byProvider[provider]?.baseURL, auth: "bearer" }) + resolutions[provider] ?? ProviderResolver.make(provider, "openai-compatible-chat") export const resolver = ProviderResolver.define({ id: ProviderResolver.make("openai-compatible", "openai-compatible-chat").provider, diff --git a/packages/opencode/src/provider/llm-bridge.ts b/packages/opencode/src/provider/llm-bridge.ts index 0d1f21a40e..63501050c4 100644 --- a/packages/opencode/src/provider/llm-bridge.ts +++ b/packages/opencode/src/provider/llm-bridge.ts @@ -65,7 +65,7 @@ const baseURL = (input: Input, resolution: ProviderResolution, options: Record => { +const authHeader = (auth: ProviderAuth, apiKey: string | undefined): Record => { if (!apiKey) return {} if (auth === "none") return {} if (auth === "anthropic-api-key") return { "x-api-key": apiKey } @@ -87,7 +87,7 @@ const reasoningEfforts = (input: Input) => REASONING_EFFORTS.has(effort as ReasoningEffort), ) -const mergeCapabilities = (base: CapabilitiesInput, override: CapabilitiesInput | undefined): CapabilitiesInput => ({ +const mergeCapabilities = (base: CapabilitiesInput, override: CapabilitiesInput): CapabilitiesInput => ({ input: { ...base.input, ...override?.input }, output: { ...base.output, ...override?.output }, tools: { ...base.tools, ...override?.tools }, @@ -95,41 +95,38 @@ const mergeCapabilities = (base: CapabilitiesInput, override: CapabilitiesInput reasoning: { ...base.reasoning, ...override?.reasoning }, }) -const capabilities = (input: Input, resolution: ProviderResolution) => - LLM.capabilities( - mergeCapabilities( - { - input: { - text: input.model.capabilities.input.text, - image: input.model.capabilities.input.image, - audio: input.model.capabilities.input.audio, - video: input.model.capabilities.input.video, - pdf: input.model.capabilities.input.pdf, - }, - output: { - text: input.model.capabilities.output.text, - reasoning: input.model.capabilities.reasoning, - }, - tools: { - calls: input.model.capabilities.toolcall, - streamingInput: resolution.protocol !== "gemini" && input.model.capabilities.toolcall, - }, - cache: { - // Both Anthropic Messages and Bedrock Converse honour positional cache - // markers — Anthropic via `cache_control` on content blocks, Bedrock via - // its `cachePoint` marker block (added to BedrockConverse in 9d7d518ac). - prompt: ["anthropic-messages", "bedrock-converse"].includes(resolution.protocol), - contentBlocks: ["anthropic-messages", "bedrock-converse"].includes(resolution.protocol), - }, - reasoning: { - efforts: reasoningEfforts(input), - summaries: resolution.protocol === "openai-responses", - encryptedContent: resolution.protocol === "openai-responses" || resolution.protocol === "anthropic-messages", - }, - }, - resolution.capabilities, - ), - ) +const capabilities = (input: Input, resolution: ProviderResolution) => { + const base: CapabilitiesInput = { + input: { + text: input.model.capabilities.input.text, + image: input.model.capabilities.input.image, + audio: input.model.capabilities.input.audio, + video: input.model.capabilities.input.video, + pdf: input.model.capabilities.input.pdf, + }, + output: { + text: input.model.capabilities.output.text, + reasoning: input.model.capabilities.reasoning, + }, + tools: { + calls: input.model.capabilities.toolcall, + streamingInput: resolution.protocol !== "gemini" && input.model.capabilities.toolcall, + }, + cache: { + // Both Anthropic Messages and Bedrock Converse honour positional cache + // markers — Anthropic via `cache_control` on content blocks, Bedrock via + // its `cachePoint` marker block (added to BedrockConverse in 9d7d518ac). + prompt: ["anthropic-messages", "bedrock-converse"].includes(resolution.protocol), + contentBlocks: ["anthropic-messages", "bedrock-converse"].includes(resolution.protocol), + }, + reasoning: { + efforts: reasoningEfforts(input), + summaries: resolution.protocol === "openai-responses", + encryptedContent: resolution.protocol === "openai-responses" || resolution.protocol === "anthropic-messages", + }, + } + return LLM.capabilities(resolution.capabilities ? mergeCapabilities(base, resolution.capabilities) : base) +} export const toModelRef = (input: Input): ModelRef | undefined => { const options = { ...input.provider.options, ...input.model.options }