fix(core): type models.dev reasoning options

This commit is contained in:
Aiden Cline 2026-06-09 19:39:42 -05:00
commit e8c63dbf83
8 changed files with 122 additions and 29 deletions

View file

@ -48,7 +48,9 @@ export const Plugin = PluginV2.define({
if (config.capabilities !== undefined) {
model.capabilities = {
tools: config.capabilities.tools,
reasoningOptions: config.capabilities.reasoningOptions?.map((option) => ({ ...option })),
reasoningOptions: config.capabilities.reasoningOptions?.map((option) =>
option.type === "effort" ? { ...option, values: [...option.values] } : { ...option },
),
input: [...config.capabilities.input],
output: [...config.capabilities.output],
}

View file

@ -2,6 +2,7 @@ import { DateTime, Schema } from "effect"
import { DateTimeUtcFromMillis } from "effect/Schema"
import { ProviderV2 } from "./provider"
import { ModelRequest } from "./model-request"
import { ModelsDev } from "./models-dev"
export const ID = Schema.String.pipe(Schema.brand("ModelV2.ID"))
export type ID = typeof ID.Type
@ -15,7 +16,7 @@ export type Family = typeof Family.Type
export const Capabilities = Schema.Struct({
tools: Schema.Boolean,
reasoningOptions: Schema.Array(Schema.Record(Schema.String, Schema.Any)).pipe(Schema.optional),
reasoningOptions: Schema.Array(ModelsDev.ResolvedReasoningOption).pipe(Schema.optional),
// mime patterns, image, audio, video/*, text/*
input: Schema.String.pipe(Schema.Array),
output: Schema.String.pipe(Schema.Array),

View file

@ -43,11 +43,64 @@ const Cost = Schema.Struct({
),
})
export const ReasoningOption = Schema.StructWithRest(Schema.Struct({ type: Schema.String }), [
Schema.Record(Schema.String, Schema.MutableJson),
const reasoningOption = <Fields extends Schema.Struct.Fields>(fields: Fields) =>
Schema.StructWithRest(Schema.Struct(fields), [Schema.Record(Schema.String, Schema.MutableJson)])
export const ReasoningOption = Schema.Union([
reasoningOption({ type: Schema.Literal("toggle") }),
reasoningOption({
type: Schema.Literal("effort"),
values: Schema.Array(Schema.NullOr(Schema.String)),
}),
reasoningOption({
type: Schema.Literal("budget_tokens"),
min: Schema.optional(Schema.Finite),
max: Schema.optional(Schema.Finite),
}),
reasoningOption({ type: Schema.String }),
])
export type ReasoningOption = typeof ReasoningOption.Type
export const ResolvedReasoningOption = Schema.Union([
Schema.Struct({ type: Schema.Literal("toggle") }),
Schema.Struct({
type: Schema.Literal("effort"),
values: Schema.Array(Schema.String),
}),
Schema.Struct({
type: Schema.Literal("budget_tokens"),
min: Schema.optional(Schema.Finite),
max: Schema.optional(Schema.Finite),
}),
])
export type ResolvedReasoningOption =
| { type: "toggle" }
| { type: "effort"; values: string[] }
| { type: "budget_tokens"; min?: number; max?: number }
export function resolveReasoningOptions(
options: readonly ReasoningOption[] | undefined,
): ResolvedReasoningOption[] | undefined {
if (!options) return
return options
.map((option): ResolvedReasoningOption | undefined => {
if (option.type === "toggle") return { type: "toggle" }
if (option.type === "effort" && Array.isArray(option.values)) {
return {
type: "effort",
values: option.values.filter((value): value is string => typeof value === "string"),
}
}
if (option.type !== "budget_tokens") return
return {
type: "budget_tokens",
...(typeof option.min === "number" && Number.isFinite(option.min) ? { min: option.min } : {}),
...(typeof option.max === "number" && Number.isFinite(option.max) ? { max: option.max } : {}),
}
})
.filter((option): option is ResolvedReasoningOption => option !== undefined)
}
export const Model = Schema.Struct({
id: Schema.String,
name: Schema.String,

View file

@ -99,7 +99,7 @@ export const ModelsDevPlugin = PluginV2.define({
}
draft.capabilities = {
tools: model.tool_call,
reasoningOptions: model.reasoning_options?.map((option) => ({ ...option })),
reasoningOptions: ModelsDev.resolveReasoningOptions(model.reasoning_options),
input: [...(model.modalities?.input ?? [])],
output: [...(model.modalities?.output ?? [])],
}