Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 32 additions & 8 deletions src/api/providers/base-openai-compatible-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { type ApiHandlerOptions, getModelMaxOutputTokens } from "../../shared/ap
import { TagMatcher } from "../../utils/tag-matcher"
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { getModelParams } from "../transform/model-params"

import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
import { DEFAULT_HEADERS } from "./constants"
Expand Down Expand Up @@ -73,7 +74,7 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
metadata?: ApiHandlerCreateMessageMetadata,
requestOptions?: OpenAI.RequestOptions,
) {
const { id: model, info } = this.getModel()
const { id: model, info, reasoning } = this.getModel()

// Centralized cap: clamp to 20% of the context window (unless provider-specific exceptions apply)
const max_tokens =
Expand All @@ -98,8 +99,14 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
}

// Add thinking parameter if reasoning is enabled and model supports it
if (this.options.enableReasoningEffort && info.supportsReasoningBinary) {
// Add reasoning_effort from centralized model params (handled by getModelParams)
if (reasoning) {
Object.assign(params, reasoning)
}

// Fallback: Add binary thinking parameter when reasoning_effort not used but
// reasoning is still enabled and model supports simple on/off thinking
if (!reasoning && this.options.enableReasoningEffort && info.supportsReasoningBinary) {
;(params as any).thinking = { type: "enabled" }
}

Expand Down Expand Up @@ -220,15 +227,20 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
}

async completePrompt(prompt: string): Promise<string> {
const { id: modelId, info: modelInfo } = this.getModel()
const { id: modelId, info: modelInfo, reasoning } = this.getModel()

const params: OpenAI.Chat.Completions.ChatCompletionCreateParams = {
model: modelId,
messages: [{ role: "user", content: prompt }],
}

// Add thinking parameter if reasoning is enabled and model supports it
if (this.options.enableReasoningEffort && modelInfo.supportsReasoningBinary) {
// Add reasoning_effort from centralized model params
if (reasoning) {
Object.assign(params, reasoning)
}

// Fallback: Add binary thinking parameter when reasoning_effort not used
if (!reasoning && this.options.enableReasoningEffort && modelInfo.supportsReasoningBinary) {
;(params as any).thinking = { type: "enabled" }
}

Expand All @@ -250,11 +262,23 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
}

override getModel() {
const id =
const providerId =
this.options.apiModelId && this.options.apiModelId in this.providerModels
? (this.options.apiModelId as ModelName)
: this.defaultProviderModelId

return { id, info: this.providerModels[id] }
// Allow user to override model info via openAiCustomModelInfo (like OpenAiHandler),
// enabling support for custom/unknown models with reasoning effort capability
const info: ModelInfo = this.options.openAiCustomModelInfo ?? this.providerModels[providerId]

const params = getModelParams({
format: "openai",
modelId: providerId,
model: info,
settings: this.options,
defaultTemperature: this.defaultTemperature,
})

return { id: providerId as string, info, ...params }
}
}
19 changes: 19 additions & 0 deletions src/api/providers/openai-compatible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ export abstract class OpenAICompatibleHandler extends BaseProvider implements Si
const openAiTools = this.convertToolsForOpenAI(metadata?.tools)
const aiSdkTools = convertToolsForAiSdk(openAiTools) as ToolSet | undefined

// Build provider options for reasoning_effort (supported by @ai-sdk/openai-compatible)
const modelReasoning = (model as any).reasoning as { reasoning_effort?: string } | undefined
const openaiCompatibleOptions = modelReasoning?.reasoning_effort
? { reasoningEffort: modelReasoning.reasoning_effort }
: undefined

// Build the request options
const requestOptions: Parameters<typeof streamText>[0] = {
model: languageModel,
Expand All @@ -174,6 +180,9 @@ export abstract class OpenAICompatibleHandler extends BaseProvider implements Si
maxOutputTokens: this.getMaxOutputTokens(),
tools: aiSdkTools,
toolChoice: this.mapToolChoice(metadata?.tool_choice),
...(openaiCompatibleOptions
? { providerOptions: { openaiCompatible: openaiCompatibleOptions } as any }
: {}),
}

// Use streamText for streaming responses
Expand All @@ -199,12 +208,22 @@ export abstract class OpenAICompatibleHandler extends BaseProvider implements Si
*/
async completePrompt(prompt: string): Promise<string> {
const languageModel = this.getLanguageModel()
const model = this.getModel()

// Build provider options for reasoning_effort
const modelReasoning = (model as any).reasoning as { reasoning_effort?: string } | undefined
const openaiCompatibleOptions = modelReasoning?.reasoning_effort
? { reasoningEffort: modelReasoning.reasoning_effort }
: undefined

const { text } = await generateText({
model: languageModel,
prompt,
maxOutputTokens: this.getMaxOutputTokens(),
temperature: this.config.temperature ?? 0,
...(openaiCompatibleOptions
? { providerOptions: { openaiCompatible: openaiCompatibleOptions } as any }
: {}),
})

return text
Expand Down
Loading