Skip to content
Merged
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
16 changes: 16 additions & 0 deletions packages/core/src/v1/config/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,29 @@ const InterleavedField = Schema.Union([
Schema.String,
])

// models.dev's `reasoning_options`: OpenCode builds the model's reasoning variants from them.
const ReasoningOption = Schema.Union([
Schema.Struct({ type: Schema.Literal("effort"), values: Schema.Array(Schema.NullOr(Schema.String)) }),
Schema.Struct({ type: Schema.Literal("toggle") }),
Schema.Struct({
type: Schema.Literal("budget_tokens"),
min: Schema.optional(Schema.Finite),
max: Schema.optional(Schema.Finite),
}),
])

export const Model = Schema.Struct({
id: Schema.optional(Schema.String),
name: Schema.optional(Schema.String),
family: Schema.optional(Schema.String),
release_date: Schema.optional(Schema.String),
attachment: Schema.optional(Schema.Boolean),
reasoning: Schema.optional(Schema.Boolean),
reasoning_options: Schema.optional(
Schema.Array(ReasoningOption).annotate({
description: "Reasoning controls the model accepts, used to build its variants",
}),
),
temperature: Schema.optional(Schema.Boolean),
tool_call: Schema.optional(Schema.Boolean),
interleaved: Schema.optional(
Expand Down
9 changes: 6 additions & 3 deletions packages/opencode/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1616,10 +1616,13 @@ const layer = Layer.effect(
release_date: model.release_date ?? existingModel?.release_date ?? "",
variants: {},
}
// Configured reasoning options replace the catalog variants; configured variants still apply on top.
const variants =
existingModel?.api.npm === parsedModel.api.npm
? (existingModel.variants ?? ProviderTransform.variants(parsedModel))
: ProviderTransform.variants(parsedModel)
model.reasoning_options !== undefined
? (ProviderTransform.reasoningVariants(model, parsedModel) ?? ProviderTransform.variants(parsedModel))
: existingModel?.api.npm === parsedModel.api.npm
? (existingModel.variants ?? ProviderTransform.variants(parsedModel))
: ProviderTransform.variants(parsedModel)
const merged = mergeDeep(variants, model.variants ?? {})
parsedModel.variants = mapValues(
pickBy(merged, (v) => !v.disabled),
Expand Down
5 changes: 4 additions & 1 deletion packages/opencode/src/provider/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1714,7 +1714,10 @@ export function schema(model: Provider.Model, schema: JSONSchema7): JSONSchema7
return schema
}

export function reasoningVariants(model: ModelsDev.Model, target: Provider.Model): Provider.Model["variants"] {
export function reasoningVariants(
model: Pick<ModelsDev.Model, "reasoning_options">,
target: Provider.Model,
): Provider.Model["variants"] {
const options = model.reasoning_options
if (options === undefined) return
if (options.length === 0) return {}
Expand Down
27 changes: 27 additions & 0 deletions packages/opencode/test/provider/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,33 @@ it.instance(
},
)

it.instance(
"model config reasoning options replace catalog variants under configured variants",
Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key")
const providers = yield* list
const models = providers[ProviderV2.ID.anthropic].models
expect(Object.keys(models["claude-sonnet-4-6"].variants ?? {})).toEqual(["low", "high"])
expect(models["claude-sonnet-4-6"].variants?.high).toMatchObject({ effort: "high", extra: true })
expect(models["claude-fixed"].variants).toEqual({})
}),
{
config: {
provider: {
anthropic: {
models: {
"claude-sonnet-4-6": {
reasoning_options: [{ type: "effort", values: ["low", "high"] }],
variants: { high: { extra: true } },
},
"claude-fixed": { id: "claude-sonnet-4-6", reasoning: true, reasoning_options: [] },
},
},
},
},
},
)

it.instance(
"model config regenerates variants when overriding the provider package",
Effect.gen(function* () {
Expand Down
17 changes: 17 additions & 0 deletions packages/sdk/js/src/v2/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1768,6 +1768,23 @@ export type ProviderConfig = {
release_date?: string
attachment?: boolean
reasoning?: boolean
/**
* Reasoning controls the model accepts, used to build its variants
*/
reasoning_options?: Array<
| {
type: "effort"
values: Array<string>
}
| {
type: "toggle"
}
| {
type: "budget_tokens"
min?: number
max?: number
}
>
temperature?: boolean
tool_call?: boolean
interleaved?:
Expand Down
Loading