fix(provider): keep reasoning options structured

This commit is contained in:
Aiden Cline 2026-06-30 23:37:21 -05:00
commit 6ea601eb37
4 changed files with 19 additions and 22 deletions

View file

@ -46,7 +46,7 @@ const Cost = Schema.Struct({
const ReasoningEffortValue = Schema.Union([Schema.Null, Schema.String])
export const ReasoningOption = Schema.Union([
export const KnownReasoningOption = Schema.Union([
Schema.Struct({
type: Schema.Literal("effort"),
values: Schema.Array(ReasoningEffortValue),
@ -60,6 +60,14 @@ export const ReasoningOption = Schema.Union([
max: Schema.optional(Schema.Finite),
}),
])
export type KnownReasoningOption = typeof KnownReasoningOption.Type
export const ReasoningOption = Schema.Union([
KnownReasoningOption,
Schema.Struct({
type: Schema.String,
}),
])
export type ReasoningOption = typeof ReasoningOption.Type
export const Model = Schema.Struct({
@ -69,9 +77,7 @@ export const Model = Schema.Struct({
release_date: Schema.String,
attachment: Schema.Boolean,
reasoning: Schema.Boolean,
// models.dev is external metadata and reasoning controls are expected to evolve.
// Keep the fetched value opaque here; provider normalization extracts the known subset.
reasoning_options: Schema.optional(Schema.Unknown),
reasoning_options: Schema.optional(Schema.Array(ReasoningOption)),
temperature: Schema.Boolean,
tool_call: Schema.Boolean,
interleaved: Schema.optional(

View file

@ -1022,7 +1022,7 @@ export const Model = Schema.Struct({
name: Schema.String,
family: optional(Schema.String),
capabilities: ProviderCapabilities,
reasoning_options: optional(Schema.Array(ModelsDev.ReasoningOption)),
reasoning_options: optional(Schema.Array(ModelsDev.KnownReasoningOption)),
cost: ProviderCost,
limit: ProviderLimit,
status: ModelStatus,

View file

@ -59,7 +59,7 @@ describe("provider model status schemas", () => {
).toBe("active")
})
test("keeps models.dev reasoning options opaque for forward compatibility", () => {
test("accepts future models.dev reasoning options without rejecting known structure", () => {
expect(() =>
Schema.decodeUnknownSync(ModelsDev.Model)({
id: "test-model",
@ -67,10 +67,11 @@ describe("provider model status schemas", () => {
release_date: "2026-01-01",
attachment: false,
reasoning: true,
reasoning_options: {
future: "shape",
values: [{ type: "new_control", values: ["turbo"] }],
},
reasoning_options: [
{ type: "effort", values: ["turbo"] },
{ type: "toggle", modes: ["auto", "off"] },
{ type: "new_control", values: ["turbo"] },
],
temperature: true,
tool_call: true,
limit: { context: 128000, output: 8192 },

View file

@ -1463,21 +1463,12 @@ test("models.dev normalization ignores unknown reasoning options and passes new
reasoning: true,
reasoning_options: [
{ type: "future_control", values: ["turbo"] },
{ type: "effort", values: ["turbo", 123, null] },
{ type: "budget_tokens", min: "1024", max: 16_000 },
"invalid",
{ type: "effort", values: ["turbo", null] },
{ type: "budget_tokens", max: 16_000 },
],
cost: { input: 1, output: 2 },
limit: { context: 128_000, output: 32_000 },
},
changed: {
id: "changed",
name: "Changed",
reasoning: true,
reasoning_options: { type: "effort", values: ["turbo"] },
cost: { input: 1, output: 2 },
limit: { context: 128_000, output: 32_000 },
},
future: {
id: "future",
name: "Future",
@ -1495,7 +1486,6 @@ test("models.dev normalization ignores unknown reasoning options and passes new
{ type: "budget_tokens", max: 16_000 },
])
expect(models.reasoner.variants).toEqual({ turbo: { reasoningEffort: "turbo" } })
expect(models.changed.reasoning_options).toBeUndefined()
expect(models.future.reasoning_options).toBeUndefined()
})