fix(provider): derive reasoning variants from models.dev
This commit is contained in:
parent
9644a008a4
commit
b41b10faeb
13 changed files with 2671 additions and 2202 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": [null, "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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,5 +10,25 @@
|
|||
"name": "Local",
|
||||
"env": [],
|
||||
"models": {}
|
||||
},
|
||||
"opencode": {
|
||||
"id": "opencode",
|
||||
"name": "OpenCode",
|
||||
"env": [],
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"models": {
|
||||
"gpt-5.5": {
|
||||
"id": "gpt-5.5",
|
||||
"name": "GPT-5.5",
|
||||
"release_date": "2026-04-23",
|
||||
"attachment": true,
|
||||
"reasoning": true,
|
||||
"reasoning_options": [{ "type": "effort", "values": [null, "none", "low", "medium", "high", "xhigh"] }],
|
||||
"temperature": false,
|
||||
"tool_call": true,
|
||||
"limit": { "context": 400000, "output": 128000 },
|
||||
"provider": { "npm": "@ai-sdk/openai", "api": "https://console.opencode.ai/inference/openai/v1" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,4 +167,94 @@ describe("ModelsDevPlugin", () => {
|
|||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("converts reasoning options into request 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-reasoning.json")
|
||||
Flag.OPENCODE_DISABLE_MODELS_FETCH = true
|
||||
return previous
|
||||
}),
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const integrations = yield* Integration.Service
|
||||
yield* ModelsDevPlugin.effect(
|
||||
host({
|
||||
catalog: catalogHost(catalog),
|
||||
integration: integrationHost(integrations),
|
||||
}),
|
||||
)
|
||||
|
||||
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("none"),
|
||||
ModelV2.VariantID.make("low"),
|
||||
ModelV2.VariantID.make("high"),
|
||||
])
|
||||
expect(model?.variants).toContainEqual({
|
||||
id: ModelV2.VariantID.make("low"),
|
||||
headers: {},
|
||||
body: {
|
||||
reasoning: { effort: "low", summary: "auto" },
|
||||
include: ["reasoning.encrypted_content"],
|
||||
},
|
||||
})
|
||||
expect(model?.variants).toContainEqual({
|
||||
id: ModelV2.VariantID.make("high"),
|
||||
headers: {},
|
||||
body: {
|
||||
reasoning: { effort: "high", summary: "auto" },
|
||||
include: ["reasoning.encrypted_content"],
|
||||
},
|
||||
})
|
||||
|
||||
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("none"),
|
||||
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"),
|
||||
headers: {},
|
||||
body: { thinking: { type: "enabled", budget_tokens: 16000 } },
|
||||
})
|
||||
expect(budgetModel?.variants).toContainEqual({
|
||||
id: ModelV2.VariantID.make("max"),
|
||||
headers: {},
|
||||
body: { thinking: { type: "enabled", budget_tokens: 64000 } },
|
||||
})
|
||||
|
||||
const effortModel = yield* catalog.model.get(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-effort"))
|
||||
expect(effortModel?.variants).toContainEqual({
|
||||
id: ModelV2.VariantID.make("low"),
|
||||
headers: {},
|
||||
body: {
|
||||
thinking: { type: "adaptive", display: "summarized" },
|
||||
output_config: { effort: "low" },
|
||||
},
|
||||
})
|
||||
}).pipe(Effect.provide(AppNodeBuilder.build(ModelsDev.node))),
|
||||
(previous) =>
|
||||
Effect.sync(() => {
|
||||
Flag.OPENCODE_MODELS_PATH = previous.path
|
||||
Flag.OPENCODE_DISABLE_MODELS_FETCH = previous.disabled
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue