Compare commits

...

7 Commits

Author SHA1 Message Date
Aiden Cline 8eef79784e fix(provider): normalize null reasoning efforts to none
The generated SDK flattens Schema.NullOr to plain strings, so the
runtime schema with (string | null)[] effort values no longer matched
the generated Provider type and typecheck failed on dev. models.dev
uses null to mean reasoning can be disabled; map it to "none" at
parse time and keep the public schema plain strings.
2026-07-12 23:35:09 -05:00
opencode-agent[bot] cf7503687a chore: generate 2026-07-12 21:13:21 +00:00
Aiden Cline 6f8e1dda15 fix(provider): derive variants from reasoning options (#36543) 2026-07-12 16:12:07 -05:00
opencode-agent[bot] 4dcfd9182c chore: generate 2026-07-12 19:42:58 +00:00
Nabs d7c0db8cee fix(openai): use codex context limits for gpt-5.6 (#36248)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-07-12 14:41:51 -05:00
Aiden Cline 184da0e42e test(opencode): refresh stale model references (#36546) 2026-07-12 13:28:36 -05:00
Aiden Cline a244d82aba test(opencode): refresh models.dev fixture (#36541) 2026-07-12 12:36:20 -05:00
9 changed files with 166182 additions and 110570 deletions
+3
View File
@@ -51,6 +51,9 @@ export const Model = Schema.Struct({
release_date: Schema.String, release_date: Schema.String,
attachment: Schema.Boolean, attachment: Schema.Boolean,
reasoning: Schema.Boolean, reasoning: Schema.Boolean,
// models.dev is external metadata and reasoning controls are expected to evolve.
// Provider normalization extracts the subset understood by this client.
reasoning_options: Schema.optional(Schema.Unknown),
temperature: Schema.Boolean, temperature: Schema.Boolean,
tool_call: Schema.Boolean, tool_call: Schema.Boolean,
interleaved: Schema.optional( interleaved: Schema.optional(
+17 -2
View File
@@ -1,5 +1,5 @@
import { describe, expect, beforeAll, beforeEach, afterAll } from "bun:test" import { describe, expect, beforeAll, beforeEach, afterAll, test } from "bun:test"
import { Effect, Layer, Ref } from "effect" import { Effect, Layer, Ref, Schema } from "effect"
import { HttpClient, HttpClientResponse } from "effect/unstable/http" import { HttpClient, HttpClientResponse } from "effect/unstable/http"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform" import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
@@ -126,6 +126,21 @@ const initialState: MockState = {
calls: [], calls: [],
} }
test("models.dev model schema keeps reasoning options permissive", () => {
const model = Schema.decodeUnknownSync(ModelsDev.Model)({
id: "acme-1",
name: "Acme One",
release_date: "2026-01-01",
attachment: false,
reasoning: true,
reasoning_options: [{ type: "future_control", value: { nested: true } }, "future-shape"],
temperature: true,
tool_call: true,
limit: { context: 128000, output: 8192 },
})
expect(model.reasoning_options).toEqual([{ type: "future_control", value: { nested: true } }, "future-shape"])
})
describe("ModelsDev Service", () => { describe("ModelsDev Service", () => {
it.live("get() returns providers from disk when cache file exists", () => it.live("get() returns providers from disk when cache file exists", () =>
Effect.gen(function* () { Effect.gen(function* () {
+7 -1
View File
@@ -303,7 +303,13 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug
input: 272_000, input: 272_000,
output: 128_000, output: 128_000,
} }
: model.limit, : model.id.includes("gpt-5.6")
? {
context: 500_000,
input: 372_000,
output: 128_000,
}
: model.limit,
}, },
]), ]),
) )
@@ -981,6 +981,21 @@ const ProviderInterleaved = Schema.Union([
}), }),
]) ])
const ProviderReasoningOption = Schema.Union([
Schema.Struct({
type: Schema.Literal("effort"),
values: Schema.Array(Schema.String),
}),
Schema.Struct({
type: Schema.Literal("toggle"),
}),
Schema.Struct({
type: Schema.Literal("budget_tokens"),
min: optional(Schema.Finite),
max: optional(Schema.Finite),
}),
])
const ProviderCapabilities = Schema.Struct({ const ProviderCapabilities = Schema.Struct({
temperature: Schema.Boolean, temperature: Schema.Boolean,
reasoning: Schema.Boolean, reasoning: Schema.Boolean,
@@ -1039,6 +1054,7 @@ export const Model = Schema.Struct({
options: Schema.Record(Schema.String, Schema.Any), options: Schema.Record(Schema.String, Schema.Any),
headers: Schema.Record(Schema.String, Schema.String), headers: Schema.Record(Schema.String, Schema.String),
release_date: Schema.String, release_date: Schema.String,
reasoning_options: optional(Schema.Array(ProviderReasoningOption)),
variants: optional(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Any))), variants: optional(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Any))),
}).annotate({ identifier: "Model" }) }).annotate({ identifier: "Model" })
export type Model = Types.DeepMutable<Schema.Schema.Type<typeof Model>> export type Model = Types.DeepMutable<Schema.Schema.Type<typeof Model>>
@@ -1202,6 +1218,40 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
return result return result
} }
type ReasoningOption = NonNullable<Model["reasoning_options"]>[number]
function reasoningOptions(input: unknown): Model["reasoning_options"] {
if (!Array.isArray(input)) return []
return input.flatMap((option) => {
const normalized = normalizeReasoningOption(option)
return normalized ? [normalized] : []
})
}
function normalizeReasoningOption(option: unknown): ReasoningOption | undefined {
if (!isRecord(option)) return
if (option.type === "effort") {
if (!Array.isArray(option.values)) return
return {
type: "effort",
// models.dev uses null to mean reasoning can be disabled; expose it as "none".
values: option.values.flatMap((value) => {
if (value === null) return ["none"]
return typeof value === "string" ? [value] : []
}),
}
}
if (option.type === "toggle") return { type: "toggle" }
if (option.type !== "budget_tokens") return
const min = typeof option.min === "number" && Number.isFinite(option.min) ? option.min : undefined
const max = typeof option.max === "number" && Number.isFinite(option.max) ? option.max : undefined
return {
type: "budget_tokens",
...(min === undefined ? {} : { min }),
...(max === undefined ? {} : { max }),
}
}
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model { function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
const base: Model = { const base: Model = {
id: ModelV2.ID.make(model.id), id: ModelV2.ID.make(model.id),
@@ -1244,6 +1294,7 @@ function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model
interleaved: model.interleaved ?? false, interleaved: model.interleaved ?? false,
}, },
release_date: model.release_date ?? "", release_date: model.release_date ?? "",
reasoning_options: reasoningOptions(model.reasoning_options),
variants: {}, variants: {},
} }
@@ -1490,6 +1541,7 @@ const layer = Layer.effect(
headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}), headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}),
family: model.family ?? existingModel?.family ?? "", family: model.family ?? existingModel?.family ?? "",
release_date: model.release_date ?? existingModel?.release_date ?? "", release_date: model.release_date ?? existingModel?.release_date ?? "",
reasoning_options: existingModel?.reasoning_options,
variants: {}, variants: {},
} }
const merged = mergeDeep(ProviderTransform.variants(parsedModel), model.variants ?? {}) const merged = mergeDeep(ProviderTransform.variants(parsedModel), model.variants ?? {})
@@ -149,6 +149,30 @@ describe("plugin.codex", () => {
await enabled.dispose?.() await enabled.dispose?.()
}) })
test("uses Codex context limits for OAuth GPT models", async () => {
const hooks = await CodexAuthPlugin({} as never)
const limit = { context: 1_050_000, input: 922_000, output: 128_000 }
const provider = {
models: Object.fromEntries(
["gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"].map((id) => [
id,
{ id, api: { id }, limit, cost: {} },
]),
),
}
const models = await hooks.provider!.models!(provider as never, { auth: { type: "oauth" } } as never)
expect(models["gpt-5.4"]?.limit).toEqual(limit)
expect(models["gpt-5.5"]?.limit).toEqual({ context: 400_000, input: 272_000, output: 128_000 })
expect(models["gpt-5.6-sol"]?.limit).toEqual({ context: 500_000, input: 372_000, output: 128_000 })
expect(models["gpt-5.6-terra"]?.limit).toEqual({ context: 500_000, input: 372_000, output: 128_000 })
expect(models["gpt-5.6-luna"]?.limit).toEqual({ context: 500_000, input: 372_000, output: 128_000 })
expect(await hooks.provider!.models!(provider as never, { auth: { type: "api" } } as never)).toBe(
provider.models as never,
)
})
test("deduplicates concurrent Codex token refreshes", async () => { test("deduplicates concurrent Codex token refreshes", async () => {
let auth = { let auth = {
type: "oauth" as const, type: "oauth" as const,
@@ -159,10 +159,10 @@ it.instance(
const providers = yield* list const providers = yield* list
expect(providers[ProviderV2.ID.anthropic]).toBeDefined() expect(providers[ProviderV2.ID.anthropic]).toBeDefined()
const models = Object.keys(providers[ProviderV2.ID.anthropic].models) const models = Object.keys(providers[ProviderV2.ID.anthropic].models)
expect(models).toContain("claude-sonnet-4-20250514") expect(models).toContain("claude-sonnet-4-6")
expect(models.length).toBe(1) expect(models.length).toBe(1)
}), }),
{ config: { provider: { anthropic: { whitelist: ["claude-sonnet-4-20250514"] } } } }, { config: { provider: { anthropic: { whitelist: ["claude-sonnet-4-6"] } } } },
) )
it.instance( it.instance(
@@ -301,10 +301,10 @@ it.instance("getModel returns model for valid provider/model", () =>
Effect.gen(function* () { Effect.gen(function* () {
yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key") yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
const provider = yield* Provider.Service const provider = yield* Provider.Service
const model = yield* provider.getModel(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-20250514")) const model = yield* provider.getModel(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-6"))
expect(model).toBeDefined() expect(model).toBeDefined()
expect(String(model.providerID)).toBe("anthropic") expect(String(model.providerID)).toBe("anthropic")
expect(String(model.id)).toBe("claude-sonnet-4-20250514") expect(String(model.id)).toBe("claude-sonnet-4-6")
const language = yield* provider.getLanguage(model) const language = yield* provider.getLanguage(model)
expect(language).toBeDefined() expect(language).toBeDefined()
}), }),
@@ -435,7 +435,7 @@ it.instance(
"model options are merged from existing model", "model options are merged from existing model",
Effect.gen(function* () { Effect.gen(function* () {
const providers = yield* list const providers = yield* list
const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-20250514"] const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-6"]
expect(model.options.customOption).toBe("custom-value") expect(model.options.customOption).toBe("custom-value")
}), }),
{ {
@@ -443,7 +443,7 @@ it.instance(
provider: { provider: {
anthropic: { anthropic: {
options: { apiKey: "test-api-key" }, options: { apiKey: "test-api-key" },
models: { "claude-sonnet-4-20250514": { options: { customOption: "custom-value" } } }, models: { "claude-sonnet-4-6": { options: { customOption: "custom-value" } } },
}, },
}, },
}, },
@@ -549,7 +549,7 @@ it.instance(
Effect.gen(function* () { Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key") yield* set("ANTHROPIC_API_KEY", "test-api-key")
const providers = yield* list const providers = yield* list
const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-20250514"] const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-6"]
expect(model.name).toBe("Custom Name for Sonnet") expect(model.name).toBe("Custom Name for Sonnet")
expect(model.capabilities.toolcall).toBe(true) expect(model.capabilities.toolcall).toBe(true)
expect(model.capabilities.attachment).toBe(true) expect(model.capabilities.attachment).toBe(true)
@@ -557,7 +557,7 @@ it.instance(
}), }),
{ {
config: { config: {
provider: { anthropic: { models: { "claude-sonnet-4-20250514": { name: "Custom Name for Sonnet" } } } }, provider: { anthropic: { models: { "claude-sonnet-4-6": { name: "Custom Name for Sonnet" } } } },
}, },
}, },
) )
@@ -590,16 +590,16 @@ it.instance(
const providers = yield* list const providers = yield* list
expect(providers[ProviderV2.ID.anthropic]).toBeDefined() expect(providers[ProviderV2.ID.anthropic]).toBeDefined()
const models = Object.keys(providers[ProviderV2.ID.anthropic].models) const models = Object.keys(providers[ProviderV2.ID.anthropic].models)
expect(models).toContain("claude-sonnet-4-20250514") expect(models).toContain("claude-sonnet-4-6")
expect(models).not.toContain("claude-opus-4-20250514") expect(models).not.toContain("claude-opus-4-6")
expect(models.length).toBe(1) expect(models.length).toBe(1)
}), }),
{ {
config: { config: {
provider: { provider: {
anthropic: { anthropic: {
whitelist: ["claude-sonnet-4-20250514", "claude-opus-4-20250514"], whitelist: ["claude-sonnet-4-6", "claude-opus-4-6"],
blacklist: ["claude-opus-4-20250514"], blacklist: ["claude-opus-4-6"],
}, },
}, },
}, },
@@ -773,9 +773,9 @@ it.instance(
const model = yield* Provider.use.getSmallModel(ProviderV2.ID.anthropic) const model = yield* Provider.use.getSmallModel(ProviderV2.ID.anthropic)
expect(model).toBeDefined() expect(model).toBeDefined()
expect(String(model?.providerID)).toBe("anthropic") expect(String(model?.providerID)).toBe("anthropic")
expect(String(model?.id)).toBe("claude-sonnet-4-20250514") expect(String(model?.id)).toBe("claude-sonnet-4-6")
}), }),
{ config: { small_model: "anthropic/claude-sonnet-4-20250514" } }, { config: { small_model: "anthropic/claude-sonnet-4-6" } },
) )
it.instance( it.instance(
@@ -1094,8 +1094,8 @@ it.instance(
it.instance("getModel returns consistent results", () => it.instance("getModel returns consistent results", () =>
Effect.gen(function* () { Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key") yield* set("ANTHROPIC_API_KEY", "test-api-key")
const model1 = yield* Provider.use.getModel(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-20250514")) const model1 = yield* Provider.use.getModel(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-6"))
const model2 = yield* Provider.use.getModel(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-20250514")) const model2 = yield* Provider.use.getModel(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-6"))
expect(model1.providerID).toEqual(model2.providerID) expect(model1.providerID).toEqual(model2.providerID)
expect(model1.id).toEqual(model2.id) expect(model1.id).toEqual(model2.id)
expect(model1).toEqual(model2) expect(model1).toEqual(model2)
@@ -1426,6 +1426,54 @@ test("models.dev normalization fills required response fields", () => {
expect(model.release_date).toBe("") expect(model.release_date).toBe("")
}) })
test("models.dev reasoning options normalize to known shapes", () => {
const provider = Provider.fromModelsDevProvider({
id: "openai",
name: "OpenAI",
env: [],
npm: "@ai-sdk/openai",
models: {
reasoner: {
id: "reasoner",
name: "Reasoner",
reasoning: true,
reasoning_options: [
{ type: "future_control", value: true },
{ type: "effort", values: [null, "high", 42] },
{ type: "toggle" },
{ type: "budget_tokens", min: 1024, max: "invalid" },
],
limit: { context: 128_000, output: 16_000 },
},
},
} as unknown as ModelsDev.Provider)
expect(provider.models.reasoner.reasoning_options).toEqual([
{ type: "effort", values: ["none", "high"] },
{ type: "toggle" },
{ type: "budget_tokens", min: 1024 },
])
})
test("models.dev models without reasoning options normalize to an empty list", () => {
const provider = Provider.fromModelsDevProvider({
id: "openai",
name: "OpenAI",
env: [],
npm: "@ai-sdk/openai",
models: {
reasoner: {
id: "gpt-5.4",
name: "Reasoner",
reasoning: true,
limit: { context: 128_000, output: 16_000 },
},
},
} as unknown as ModelsDev.Provider)
expect(provider.models.reasoner.reasoning_options).toEqual([])
})
test("public provider info omits invalid models", () => { test("public provider info omits invalid models", () => {
const provider = Provider.fromModelsDevProvider({ const provider = Provider.fromModelsDevProvider({
id: "test", id: "test",
@@ -1457,7 +1505,7 @@ it.instance("model variants are generated for reasoning models", () =>
yield* set("ANTHROPIC_API_KEY", "test-api-key") yield* set("ANTHROPIC_API_KEY", "test-api-key")
const providers = yield* list const providers = yield* list
// Claude sonnet 4 has reasoning capability // Claude sonnet 4 has reasoning capability
const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-20250514"] const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-6"]
expect(model.capabilities.reasoning).toBe(true) expect(model.capabilities.reasoning).toBe(true)
expect(model.variants).toBeDefined() expect(model.variants).toBeDefined()
expect(Object.keys(model.variants!).length).toBeGreaterThan(0) expect(Object.keys(model.variants!).length).toBeGreaterThan(0)
@@ -1469,7 +1517,7 @@ it.instance(
Effect.gen(function* () { Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key") yield* set("ANTHROPIC_API_KEY", "test-api-key")
const providers = yield* list const providers = yield* list
const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-20250514"] const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-6"]
expect(model.variants).toBeDefined() expect(model.variants).toBeDefined()
expect(model.variants!["high"]).toBeUndefined() expect(model.variants!["high"]).toBeUndefined()
// max variant should still exist // max variant should still exist
@@ -1479,7 +1527,7 @@ it.instance(
config: { config: {
provider: { provider: {
anthropic: { anthropic: {
models: { "claude-sonnet-4-20250514": { variants: { high: { disabled: true } } } }, models: { "claude-sonnet-4-6": { variants: { high: { disabled: true } } } },
}, },
}, },
}, },
@@ -1491,7 +1539,7 @@ it.instance(
Effect.gen(function* () { Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key") yield* set("ANTHROPIC_API_KEY", "test-api-key")
const providers = yield* list const providers = yield* list
const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-20250514"] const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-6"]
expect(model.variants!["high"]).toBeDefined() expect(model.variants!["high"]).toBeDefined()
expect(model.variants!["high"].thinking.budgetTokens).toBe(20000) expect(model.variants!["high"].thinking.budgetTokens).toBe(20000)
}), }),
@@ -1500,7 +1548,7 @@ it.instance(
provider: { provider: {
anthropic: { anthropic: {
models: { models: {
"claude-sonnet-4-20250514": { "claude-sonnet-4-6": {
variants: { high: { thinking: { type: "enabled", budgetTokens: 20000 } } }, variants: { high: { thinking: { type: "enabled", budgetTokens: 20000 } } },
}, },
}, },
@@ -1515,7 +1563,7 @@ it.instance(
Effect.gen(function* () { Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key") yield* set("ANTHROPIC_API_KEY", "test-api-key")
const providers = yield* list const providers = yield* list
const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-20250514"] const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-6"]
expect(model.variants!["max"]).toBeDefined() expect(model.variants!["max"]).toBeDefined()
expect(model.variants!["max"].disabled).toBeUndefined() expect(model.variants!["max"].disabled).toBeUndefined()
expect(model.variants!["max"].customField).toBe("test") expect(model.variants!["max"].customField).toBe("test")
@@ -1525,7 +1573,7 @@ it.instance(
provider: { provider: {
anthropic: { anthropic: {
models: { models: {
"claude-sonnet-4-20250514": { "claude-sonnet-4-6": {
variants: { max: { disabled: false, customField: "test" } }, variants: { max: { disabled: false, customField: "test" } },
}, },
}, },
@@ -1540,7 +1588,7 @@ it.instance(
Effect.gen(function* () { Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key") yield* set("ANTHROPIC_API_KEY", "test-api-key")
const providers = yield* list const providers = yield* list
const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-20250514"] const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-6"]
expect(model.variants).toBeDefined() expect(model.variants).toBeDefined()
expect(Object.keys(model.variants!).length).toBe(0) expect(Object.keys(model.variants!).length).toBe(0)
}), }),
@@ -1549,8 +1597,13 @@ it.instance(
provider: { provider: {
anthropic: { anthropic: {
models: { models: {
"claude-sonnet-4-20250514": { "claude-sonnet-4-6": {
variants: { high: { disabled: true }, max: { disabled: true } }, variants: {
low: { disabled: true },
medium: { disabled: true },
high: { disabled: true },
max: { disabled: true },
},
}, },
}, },
}, },
@@ -1564,7 +1617,7 @@ it.instance(
Effect.gen(function* () { Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key") yield* set("ANTHROPIC_API_KEY", "test-api-key")
const providers = yield* list const providers = yield* list
const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-20250514"] const model = providers[ProviderV2.ID.anthropic].models["claude-sonnet-4-6"]
expect(model.variants!["high"]).toBeDefined() expect(model.variants!["high"]).toBeDefined()
// Should have both the generated thinking config and the custom option // Should have both the generated thinking config and the custom option
expect(model.variants!["high"].thinking).toBeDefined() expect(model.variants!["high"].thinking).toBeDefined()
@@ -1575,7 +1628,7 @@ it.instance(
provider: { provider: {
anthropic: { anthropic: {
models: { models: {
"claude-sonnet-4-20250514": { variants: { high: { extraOption: "custom-value" } } }, "claude-sonnet-4-6": { variants: { high: { extraOption: "custom-value" } } },
}, },
}, },
}, },
File diff suppressed because it is too large Load Diff
+14
View File
@@ -2100,6 +2100,20 @@ export type Model = {
[key: string]: string [key: string]: string
} }
release_date: string release_date: string
reasoning_options?: Array<
| {
type: "effort"
values: Array<string>
}
| {
type: "toggle"
}
| {
type: "budget_tokens"
min?: number
max?: number
}
>
variants?: { variants?: {
[key: string]: { [key: string]: {
[key: string]: unknown [key: string]: unknown
+52
View File
@@ -21777,6 +21777,58 @@
"release_date": { "release_date": {
"type": "string" "type": "string"
}, },
"reasoning_options": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["effort"]
},
"values": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["type", "values"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["toggle"]
}
},
"required": ["type"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["budget_tokens"]
},
"min": {
"type": "number"
},
"max": {
"type": "number"
}
},
"required": ["type"],
"additionalProperties": false
}
]
}
},
"variants": { "variants": {
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {