mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-24 04:01:10 -04:00
f7dcfc2680
Parse the curated reasoning_options field from models.dev api.json and use its effort values to generate reasoning variants instead of the hardcoded per-package tables, in both the v1 provider catalog and the v2 catalog plugin. - core: ModelsDev.ReasoningOption discriminated union (toggle | effort | budget_tokens); effort values stay open strings and unknown option types are tolerated since api.json is cast, not decoded - core: ReasoningVariants shared per-package effort encoder used by v1 ProviderTransform.variants and the v2 ModelsDevPlugin - core: v2 catalog generates effort variants from reasoning_options; curated experimental modes win id collisions; anthropic profile gains the effort semantic - llm: anthropic protocol supports adaptive thinking, lowers effort to output_config.effort, and sends the effort-2025-11-24 beta header - opencode: resolved Provider.Model carries capabilities.reasoningOptions (unknown types and null effort values dropped at the mapping boundary); config models accept reasoning_options; models without usable effort data fall back to the hardcoded tables unchanged Catalog-wide audit vs live api.json: 2975 models byte-identical, 38 diffs, all data correcting stale hardcoded effort lists.
117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
import { describe, expect } from "bun:test"
|
|
import { Effect, Layer } from "effect"
|
|
import { Catalog } from "@opencode-ai/core/catalog"
|
|
import { EventV2 } from "@opencode-ai/core/event"
|
|
import { Location } from "@opencode-ai/core/location"
|
|
import { ModelV2 } from "@opencode-ai/core/model"
|
|
import { ModelsDev } from "@opencode-ai/core/models-dev"
|
|
import { ModelsDevPlugin } from "@opencode-ai/core/plugin/models-dev"
|
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
|
import { location } from "./fixture/location"
|
|
import { testEffect } from "./lib/effect"
|
|
|
|
const model = (input: Partial<ModelsDev.Model> & { id: string }): ModelsDev.Model => ({
|
|
name: input.id,
|
|
release_date: "2026-01-01",
|
|
attachment: false,
|
|
reasoning: true,
|
|
temperature: true,
|
|
tool_call: true,
|
|
limit: { context: 200_000, output: 64_000 },
|
|
...input,
|
|
})
|
|
|
|
const fixture: Record<string, ModelsDev.Provider> = {
|
|
anthropic: {
|
|
id: "anthropic",
|
|
name: "Anthropic",
|
|
env: [],
|
|
npm: "@ai-sdk/anthropic",
|
|
models: {
|
|
"claude-sonnet-4-6": model({
|
|
id: "claude-sonnet-4-6",
|
|
reasoning_options: [
|
|
{ type: "effort", values: ["low", "medium", "high", "max"] },
|
|
{ type: "budget_tokens", min: 1024 },
|
|
],
|
|
}),
|
|
},
|
|
},
|
|
compat: {
|
|
id: "compat",
|
|
name: "Compat",
|
|
env: [],
|
|
npm: "@ai-sdk/openai-compatible",
|
|
models: {
|
|
"deepseek-v4": model({
|
|
id: "deepseek-v4",
|
|
reasoning_options: [{ type: "toggle" }, { type: "effort", values: [null, "high", "max"] }],
|
|
experimental: {
|
|
modes: {
|
|
high: { provider: { body: { reasoning_effort: "high", custom: true } } },
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
},
|
|
}
|
|
|
|
const locationLayer = Layer.succeed(
|
|
Location.Service,
|
|
Location.Service.of(location({ directory: AbsolutePath.make("test") })),
|
|
)
|
|
const modelsDevLayer = Layer.succeed(
|
|
ModelsDev.Service,
|
|
ModelsDev.Service.of({
|
|
get: () => Effect.succeed(fixture),
|
|
refresh: () => Effect.void,
|
|
}),
|
|
)
|
|
const it = testEffect(
|
|
Layer.mergeAll(modelsDevLayer, Catalog.locationLayer).pipe(
|
|
Layer.provideMerge(EventV2.defaultLayer),
|
|
Layer.provideMerge(locationLayer),
|
|
),
|
|
)
|
|
|
|
describe("ModelsDevPlugin reasoning_options", () => {
|
|
it.effect("generates anthropic effort variants as semantic thinking + effort options", () =>
|
|
Effect.gen(function* () {
|
|
yield* ModelsDevPlugin.effect
|
|
const catalog = yield* Catalog.Service
|
|
const info = yield* catalog.model.get(ProviderV2.ID.make("anthropic"), ModelV2.ID.make("claude-sonnet-4-6"))
|
|
expect(info.variants.map((variant) => variant.id)).toEqual(
|
|
["low", "medium", "high", "max"].map((id) => ModelV2.VariantID.make(id)),
|
|
)
|
|
expect(info.variants[2]).toMatchObject({
|
|
id: "high",
|
|
headers: {},
|
|
body: {},
|
|
options: { thinking: { type: "adaptive" }, effort: "high" },
|
|
})
|
|
}),
|
|
)
|
|
|
|
it.effect("merges effort variants after curated experimental modes, skipping null values and collisions", () =>
|
|
Effect.gen(function* () {
|
|
yield* ModelsDevPlugin.effect
|
|
const catalog = yield* Catalog.Service
|
|
const info = yield* catalog.model.get(ProviderV2.ID.make("compat"), ModelV2.ID.make("deepseek-v4"))
|
|
expect(info.variants.map((variant) => variant.id)).toEqual(["high", "max"].map((id) => ModelV2.VariantID.make(id)))
|
|
// curated mode wins the "high" id; its body keys survive partitioning
|
|
expect(info.variants[0]).toMatchObject({
|
|
id: "high",
|
|
body: { custom: true },
|
|
options: { reasoningEffort: "high" },
|
|
})
|
|
// data-driven effort variant for "max" uses the openai-compatible encoding
|
|
expect(info.variants[1]).toMatchObject({
|
|
id: "max",
|
|
body: {},
|
|
options: { reasoningEffort: "max" },
|
|
})
|
|
}),
|
|
)
|
|
})
|