fix(core): derive models.dev reasoning variants (#34726)
This commit is contained in:
parent
cfd35c9354
commit
140224b0fc
29 changed files with 457 additions and 119 deletions
67
packages/core/test/plugin/fixtures/models-dev-reasoning.json
Normal file
67
packages/core/test/plugin/fixtures/models-dev-reasoning.json
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"openai": {
|
||||
"id": "openai",
|
||||
"name": "OpenAI",
|
||||
"env": ["OPENAI_API_KEY"],
|
||||
"npm": "@ai-sdk/openai",
|
||||
"api": "https://api.openai.com/v1",
|
||||
"models": {
|
||||
"gpt-reasoning": {
|
||||
"id": "gpt-reasoning",
|
||||
"name": "GPT Reasoning",
|
||||
"release_date": "2026-01-01",
|
||||
"attachment": false,
|
||||
"reasoning": true,
|
||||
"reasoning_options": [
|
||||
{ "type": "effort", "values": ["low", "high"] },
|
||||
{ "type": "budget_tokens", "min": 1024, "max": 64000 },
|
||||
{ "type": "toggle" }
|
||||
],
|
||||
"temperature": true,
|
||||
"tool_call": true,
|
||||
"limit": { "context": 128000, "output": 8192 },
|
||||
"experimental": {
|
||||
"modes": {
|
||||
"high": {
|
||||
"provider": {
|
||||
"headers": { "x-mode": "high" },
|
||||
"body": { "service_tier": "priority" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"anthropic": {
|
||||
"id": "anthropic",
|
||||
"name": "Anthropic",
|
||||
"env": ["ANTHROPIC_API_KEY"],
|
||||
"npm": "@ai-sdk/anthropic",
|
||||
"api": "https://api.anthropic.com/v1",
|
||||
"models": {
|
||||
"claude-budget": {
|
||||
"id": "claude-budget",
|
||||
"name": "Claude Budget",
|
||||
"release_date": "2026-01-01",
|
||||
"attachment": false,
|
||||
"reasoning": true,
|
||||
"reasoning_options": [{ "type": "budget_tokens", "min": 1024, "max": 64000 }],
|
||||
"temperature": true,
|
||||
"tool_call": true,
|
||||
"limit": { "context": 128000, "output": 8192 }
|
||||
},
|
||||
"claude-effort": {
|
||||
"id": "claude-effort",
|
||||
"name": "Claude Effort",
|
||||
"release_date": "2026-01-01",
|
||||
"attachment": false,
|
||||
"reasoning": true,
|
||||
"reasoning_options": [{ "type": "effort", "values": ["low"] }],
|
||||
"temperature": true,
|
||||
"tool_call": true,
|
||||
"limit": { "context": 128000, "output": 8192 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -288,7 +288,7 @@ function providerInfo(value: ProviderV2.MutableInfo) {
|
|||
return {
|
||||
...value,
|
||||
api: { ...value.api, settings: value.api.settings && { ...value.api.settings } },
|
||||
request: { headers: { ...value.request.headers }, body: { ...value.request.body } },
|
||||
request: { settings: { ...value.request.settings }, headers: { ...value.request.headers }, body: { ...value.request.body } },
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -303,11 +303,13 @@ function modelInfo(value: ModelV2.Info | ModelV2.MutableInfo) {
|
|||
},
|
||||
request: {
|
||||
...value.request,
|
||||
settings: { ...value.request.settings },
|
||||
headers: { ...value.request.headers },
|
||||
body: { ...value.request.body },
|
||||
},
|
||||
variants: value.variants.map((variant) => ({
|
||||
...variant,
|
||||
settings: { ...variant.settings },
|
||||
headers: { ...variant.headers },
|
||||
body: { ...variant.body },
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -168,14 +168,14 @@ describe("ModelsDevPlugin", () => {
|
|||
),
|
||||
)
|
||||
|
||||
it.effect("derives OpenAI reasoning variants from models.dev reasoning options", () =>
|
||||
it.effect("converts reasoning options into settings variants", () =>
|
||||
Effect.acquireUseRelease(
|
||||
Effect.sync(() => {
|
||||
const previous = {
|
||||
path: Flag.OPENCODE_MODELS_PATH,
|
||||
disabled: Flag.OPENCODE_DISABLE_MODELS_FETCH,
|
||||
}
|
||||
Flag.OPENCODE_MODELS_PATH = path.join(import.meta.dir, "fixtures", "models-dev.json")
|
||||
Flag.OPENCODE_MODELS_PATH = path.join(import.meta.dir, "fixtures", "models-dev-reasoning.json")
|
||||
Flag.OPENCODE_DISABLE_MODELS_FETCH = true
|
||||
return previous
|
||||
}),
|
||||
|
|
@ -183,17 +183,6 @@ describe("ModelsDevPlugin", () => {
|
|||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const integrations = yield* Integration.Service
|
||||
yield* catalog.transform((catalog) => {
|
||||
catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("gpt-5.5"), (model) => {
|
||||
model.variants = [
|
||||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
headers: { custom: "true" },
|
||||
body: { custom: true },
|
||||
},
|
||||
]
|
||||
})
|
||||
})
|
||||
yield* ModelsDevPlugin.effect(
|
||||
host({
|
||||
catalog: catalogHost(catalog),
|
||||
|
|
@ -201,42 +190,67 @@ describe("ModelsDevPlugin", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("gpt-5.5")))?.variants).toEqual([
|
||||
{
|
||||
id: ModelV2.VariantID.make("none"),
|
||||
headers: {},
|
||||
body: {
|
||||
include: ["reasoning.encrypted_content"],
|
||||
reasoning: { effort: "none", summary: "auto" },
|
||||
},
|
||||
},
|
||||
expect.objectContaining({
|
||||
id: "low",
|
||||
body: {
|
||||
include: ["reasoning.encrypted_content"],
|
||||
reasoning: { effort: "low", summary: "auto" },
|
||||
},
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "medium",
|
||||
body: {
|
||||
include: ["reasoning.encrypted_content"],
|
||||
reasoning: { effort: "medium", summary: "auto" },
|
||||
},
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "high",
|
||||
headers: { custom: "true" },
|
||||
body: { custom: true },
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "xhigh",
|
||||
body: {
|
||||
include: ["reasoning.encrypted_content"],
|
||||
reasoning: { effort: "xhigh", summary: "auto" },
|
||||
},
|
||||
}),
|
||||
const model = yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-reasoning"))
|
||||
expect(model?.variants.map((variant) => variant.id)).toEqual([
|
||||
ModelV2.VariantID.make("low"),
|
||||
ModelV2.VariantID.make("high"),
|
||||
])
|
||||
expect(model?.variants).toContainEqual({
|
||||
id: ModelV2.VariantID.make("low"),
|
||||
settings: {
|
||||
reasoningEffort: "low",
|
||||
reasoningSummary: "auto",
|
||||
include: ["reasoning.encrypted_content"],
|
||||
},
|
||||
headers: {},
|
||||
body: {},
|
||||
})
|
||||
expect(model?.variants).toContainEqual({
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
settings: {
|
||||
reasoningEffort: "high",
|
||||
reasoningSummary: "auto",
|
||||
include: ["reasoning.encrypted_content"],
|
||||
},
|
||||
headers: {},
|
||||
body: {},
|
||||
})
|
||||
|
||||
const mode = yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-reasoning-high"))
|
||||
expect(mode).toMatchObject({
|
||||
id: "gpt-reasoning-high",
|
||||
name: "GPT Reasoning High",
|
||||
request: {
|
||||
headers: { "x-mode": "high" },
|
||||
body: { service_tier: "priority" },
|
||||
},
|
||||
})
|
||||
expect(mode?.variants.map((variant) => variant.id)).toEqual([
|
||||
ModelV2.VariantID.make("low"),
|
||||
ModelV2.VariantID.make("high"),
|
||||
])
|
||||
|
||||
const budgetModel = yield* catalog.model.get(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-budget"))
|
||||
expect(budgetModel?.variants).toContainEqual({
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
settings: { thinking: { type: "enabled", budgetTokens: 16000 } },
|
||||
headers: {},
|
||||
body: {},
|
||||
})
|
||||
expect(budgetModel?.variants).toContainEqual({
|
||||
id: ModelV2.VariantID.make("max"),
|
||||
settings: { thinking: { type: "enabled", budgetTokens: 64000 } },
|
||||
headers: {},
|
||||
body: {},
|
||||
})
|
||||
|
||||
const anthropicEffortModel = yield* catalog.model.get(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-effort"))
|
||||
expect(anthropicEffortModel?.variants).toContainEqual({
|
||||
id: ModelV2.VariantID.make("low"),
|
||||
settings: { thinking: { type: "adaptive", display: "summarized" }, effort: "low" },
|
||||
headers: {},
|
||||
body: {},
|
||||
})
|
||||
}).pipe(Effect.provide(AppNodeBuilder.build(ModelsDev.node))),
|
||||
(previous) =>
|
||||
Effect.sync(() => {
|
||||
|
|
@ -245,5 +259,4 @@ describe("ModelsDevPlugin", () => {
|
|||
}),
|
||||
),
|
||||
)
|
||||
|
||||
})
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ describe("OpencodePlugin", () => {
|
|||
model.variants = [
|
||||
{
|
||||
id: ModelV2.VariantID.make("custom"),
|
||||
settings: {},
|
||||
headers: { "x-custom": "true" },
|
||||
body: { custom: true },
|
||||
},
|
||||
|
|
@ -177,7 +178,7 @@ describe("OpencodePlugin", () => {
|
|||
url: `${server.url.origin}/v1`,
|
||||
},
|
||||
})
|
||||
expect(provider.request).toEqual({ headers: { "x-org-id": "org" }, body: { custom: "value" } })
|
||||
expect(provider.request).toEqual({ settings: {}, headers: { "x-org-id": "org" }, body: { custom: "value" } })
|
||||
expect(yield* (yield* Integration.Service).get(Integration.ID.make("remote"))).toBeUndefined()
|
||||
|
||||
const model = required(yield* catalog.model.get(ProviderV2.ID.make("remote"), ModelV2.ID.make("model")))
|
||||
|
|
@ -192,11 +193,13 @@ describe("OpencodePlugin", () => {
|
|||
expect(model.variants).toEqual([
|
||||
{
|
||||
id: ModelV2.VariantID.make("custom"),
|
||||
settings: {},
|
||||
headers: { "x-custom": "true" },
|
||||
body: { custom: true },
|
||||
},
|
||||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
settings: {},
|
||||
headers: {},
|
||||
body: { temperature: 0.2 },
|
||||
},
|
||||
|
|
@ -359,6 +362,7 @@ describe("OpencodePlugin", () => {
|
|||
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
|
||||
api: { type: "aisdk", package: "test-provider" },
|
||||
request: {
|
||||
settings: {},
|
||||
headers: {},
|
||||
body: { apiKey: "configured" },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ describe("VariantPlugin", () => {
|
|||
yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) }))
|
||||
|
||||
expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([
|
||||
expect.objectContaining({ id: "high", body: { reasoning_effort: "high" } }),
|
||||
expect.objectContaining({ id: "max", body: { reasoning_effort: "max" } }),
|
||||
expect.objectContaining({ id: "high", settings: { reasoningEffort: "high" } }),
|
||||
expect.objectContaining({ id: "max", settings: { reasoningEffort: "max" } }),
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
|
@ -53,14 +53,14 @@ describe("VariantPlugin", () => {
|
|||
type: "aisdk",
|
||||
package: "@ai-sdk/openai-compatible",
|
||||
}
|
||||
model.variants = [{ id: ModelV2.VariantID.make("high"), headers: { custom: "true" }, body: {} }]
|
||||
model.variants = [{ id: ModelV2.VariantID.make("high"), settings: {}, headers: { custom: "true" }, body: {} }]
|
||||
})
|
||||
})
|
||||
yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) }))
|
||||
|
||||
expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([
|
||||
expect.objectContaining({ id: "high", headers: { custom: "true" } }),
|
||||
expect.objectContaining({ id: "max", body: { reasoning_effort: "max" } }),
|
||||
expect.objectContaining({ id: "max", settings: { reasoningEffort: "max" } }),
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ const model = (api: Api, variants: ModelV2.Info["variants"] = []) =>
|
|||
api: { id: ModelV2.ID.make("api-test-model"), ...api },
|
||||
capabilities: { tools: true, input: ["text"], output: ["text"] },
|
||||
request: {
|
||||
settings: {},
|
||||
headers: { "x-test": "header" },
|
||||
body: { apiKey: "secret", custom_extension: { enabled: true } },
|
||||
},
|
||||
|
|
@ -83,7 +84,7 @@ describe("SessionRunnerModel", () => {
|
|||
url: "https://compatible.example/v1",
|
||||
settings: { apiKey: "settings-secret", compatibility: "strict" },
|
||||
}),
|
||||
request: { headers: {}, body: {} },
|
||||
request: { settings: {}, headers: {}, body: {} },
|
||||
}),
|
||||
)
|
||||
const request = LLM.request({ model: resolved, prompt: "Hello" })
|
||||
|
|
@ -100,17 +101,17 @@ describe("SessionRunnerModel", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("overlays selected OpenAI Session variant bodies", () =>
|
||||
it.effect("overlays selected OpenAI Session variant settings and bodies", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }, [
|
||||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
settings: { reasoningEffort: "high" },
|
||||
headers: { "x-variant": "high" },
|
||||
body: {
|
||||
store: false,
|
||||
service_tier: "priority",
|
||||
temperature: 0.2,
|
||||
reasoning: { effort: "high" },
|
||||
},
|
||||
},
|
||||
])
|
||||
|
|
@ -137,7 +138,9 @@ describe("SessionRunnerModel", () => {
|
|||
store: false,
|
||||
service_tier: "priority",
|
||||
temperature: 0.2,
|
||||
reasoning: { effort: "high" },
|
||||
})
|
||||
expect(resolved.route.defaults.providerOptions).toEqual({
|
||||
openai: { store: false, reasoningEffort: "high" },
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
|
@ -149,6 +152,7 @@ describe("SessionRunnerModel", () => {
|
|||
[
|
||||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
settings: {},
|
||||
headers: {},
|
||||
body: { store: false, reasoning_effort: "high" },
|
||||
},
|
||||
|
|
@ -205,13 +209,14 @@ describe("SessionRunnerModel", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("overlays selected Anthropic Session variant bodies", () =>
|
||||
it.effect("overlays selected Anthropic Session variant settings", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = model({ type: "aisdk", package: "@ai-sdk/anthropic", url: "https://anthropic.example/v1" }, [
|
||||
{
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
settings: { thinking: { type: "enabled", budgetTokens: 12000 } },
|
||||
headers: {},
|
||||
body: { thinking: { type: "enabled", budget_tokens: 12000 } },
|
||||
body: {},
|
||||
},
|
||||
])
|
||||
const session = SessionV2.Info.make({
|
||||
|
|
@ -229,7 +234,9 @@ describe("SessionRunnerModel", () => {
|
|||
|
||||
expect(resolved.route.defaults.http?.body).toEqual({
|
||||
custom_extension: { enabled: true },
|
||||
thinking: { type: "enabled", budget_tokens: 12000 },
|
||||
})
|
||||
expect(resolved.route.defaults.providerOptions).toEqual({
|
||||
anthropic: { thinking: { type: "enabled", budgetTokens: 12000 } },
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
|
@ -252,7 +259,7 @@ describe("SessionRunnerModel", () => {
|
|||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
ModelV2.Info.make({
|
||||
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
|
||||
request: { headers: {}, body: {} },
|
||||
request: { settings: {}, headers: {}, body: {} },
|
||||
}),
|
||||
Credential.Key.make({ type: "key", key: "secret" }),
|
||||
)
|
||||
|
|
@ -275,7 +282,7 @@ describe("SessionRunnerModel", () => {
|
|||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
ModelV2.Info.make({
|
||||
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
|
||||
request: { headers: {}, body: { apiKey: "configured-secret" } },
|
||||
request: { settings: {}, headers: {}, body: { apiKey: "configured-secret" } },
|
||||
}),
|
||||
credential,
|
||||
)
|
||||
|
|
@ -297,7 +304,7 @@ describe("SessionRunnerModel", () => {
|
|||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||
ModelV2.Info.make({
|
||||
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
|
||||
request: { headers: {}, body: {} },
|
||||
request: { settings: {}, headers: {}, body: {} },
|
||||
}),
|
||||
Credential.OAuth.make({
|
||||
type: "oauth",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue