-
Notifications
You must be signed in to change notification settings - Fork 2.9k
fix(cli): default step models to interleaved reasoning #11879
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1160,7 +1160,12 @@ function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model | |
| video: model.modalities?.output?.includes("video") ?? false, | ||
| pdf: model.modalities?.output?.includes("pdf") ?? false, | ||
| }, | ||
| interleaved: model.interleaved ?? false, | ||
| interleaved: | ||
| model.interleaved ?? | ||
| ((model.provider?.npm ?? provider.npm ?? "@ai-sdk/openai-compatible") === "@ai-sdk/openai-compatible" && | ||
| model.id.includes("step") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: Broad substring match on "step" may false-positive on unrelated model IDs
Reply with |
||
| ? { field: "reasoning_content" as const } | ||
| : false), | ||
| }, | ||
| release_date: model.release_date ?? "", | ||
| variants: {}, | ||
|
|
@@ -1397,10 +1402,11 @@ export const layer = Layer.effect( | |
| }, | ||
| interleaved: | ||
| model.interleaved ?? | ||
| existingModel?.capabilities.interleaved ?? | ||
| (!existingModel && apiNpm === "@ai-sdk/openai-compatible" && apiID.includes("deepseek") | ||
| ? { field: "reasoning_content" } | ||
| : false), | ||
| (existingModel?.capabilities.interleaved | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: Fallback now overrides a previously-computed Before this change, the fallback here was gated by Reply with |
||
| ? existingModel.capabilities.interleaved | ||
| : apiNpm === "@ai-sdk/openai-compatible" && (apiID.includes("deepseek") || apiID.includes("step")) | ||
| ? { field: "reasoning_content" as const } | ||
| : false), | ||
| }, | ||
| cost: { | ||
| input: model?.cost?.input ?? existingModel?.cost?.input ?? 0, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| import { expect, test } from "bun:test" | ||
| import { Effect } from "effect" | ||
| import { ModelsDev } from "@opencode-ai/core/models-dev" | ||
| import { Provider } from "../../../src/provider/provider" | ||
| import { ProviderID } from "../../../src/provider/schema" | ||
| import { testEffect } from "../../lib/effect" | ||
|
|
||
| const it = testEffect(Provider.defaultLayer) | ||
|
|
||
| const base = (id: string): ModelsDev.Model => ({ | ||
| id, | ||
| name: id, | ||
| release_date: "2026-01-01", | ||
| attachment: false, | ||
| reasoning: true, | ||
| temperature: true, | ||
| tool_call: true, | ||
| limit: { | ||
| context: 128000, | ||
| output: 8192, | ||
| }, | ||
| }) | ||
|
|
||
| test("StepFun catalog models default to interleaved reasoning_content", () => { | ||
| const provider = Provider.fromModelsDevProvider({ | ||
| id: "stepfun", | ||
| name: "StepFun", | ||
| env: ["STEPFUN_API_KEY"], | ||
| npm: "@ai-sdk/openai-compatible", | ||
| api: "https://api.stepfun.com/v1", | ||
| models: { | ||
| "step-3.5-flash": base("step-3.5-flash"), | ||
| }, | ||
| }) | ||
|
|
||
| expect(provider.models["step-3.5-flash"].capabilities.interleaved).toEqual({ | ||
| field: "reasoning_content", | ||
| }) | ||
| }) | ||
|
|
||
| test("StepFun provider does not default when model ID does not include step", () => { | ||
| const provider = Provider.fromModelsDevProvider({ | ||
| id: "stepfun", | ||
| name: "StepFun", | ||
| env: ["STEPFUN_API_KEY"], | ||
| npm: "@ai-sdk/openai-compatible", | ||
| api: "https://api.stepfun.com/v1", | ||
| models: { | ||
| custom: { | ||
| ...base("custom"), | ||
| name: "Custom Model", | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| expect(provider.models["custom"].capabilities.interleaved).toBe(false) | ||
| }) | ||
|
|
||
| test("catalog DeepSeek models do not default to interleaved reasoning_content", () => { | ||
| const provider = Provider.fromModelsDevProvider({ | ||
| id: "custom-provider", | ||
| name: "Custom Provider", | ||
| env: ["CUSTOM_API_KEY"], | ||
| npm: "@ai-sdk/openai-compatible", | ||
| api: "https://api.custom.com/v1", | ||
| models: { | ||
| "deepseek-r1": base("deepseek-r1"), | ||
| }, | ||
| }) | ||
|
|
||
| expect(provider.models["deepseek-r1"].capabilities.interleaved).toBe(false) | ||
| }) | ||
|
|
||
| test("catalog model IDs containing step default to interleaved reasoning_content", () => { | ||
| const provider = Provider.fromModelsDevProvider({ | ||
| id: "custom-provider", | ||
| name: "Custom Provider", | ||
| env: ["CUSTOM_API_KEY"], | ||
| npm: "@ai-sdk/openai-compatible", | ||
| api: "https://api.custom.com/v1", | ||
| models: { | ||
| space: base("step 3.5 flash"), | ||
| hyphen: base("step-3.5-flash"), | ||
| dot: base("step.3.5.flash"), | ||
| underscore: base("step_3_5_flash"), | ||
| }, | ||
| }) | ||
|
|
||
| for (const model of Object.values(provider.models)) { | ||
| expect(model.capabilities.interleaved).toEqual({ | ||
| field: "reasoning_content", | ||
| }) | ||
| } | ||
| }) | ||
|
|
||
| test("catalog model IDs containing step do not default for non-openai-compatible providers", () => { | ||
| const provider = Provider.fromModelsDevProvider({ | ||
| id: "custom-provider", | ||
| name: "Custom Provider", | ||
| env: ["CUSTOM_API_KEY"], | ||
| npm: "@kilocode/kilo-gateway", | ||
| api: "https://api.custom.com/v1", | ||
| models: { | ||
| custom: base("step-3.5-flash"), | ||
| }, | ||
| }) | ||
|
|
||
| expect(provider.models["custom"].capabilities.interleaved).toBe(false) | ||
| }) | ||
|
|
||
| it.instance( | ||
| "configured StepFun and step model IDs default to interleaved reasoning_content", | ||
| Effect.gen(function* () { | ||
| const providers = yield* Provider.use.list() | ||
| const stepfun = providers[ProviderID.make("stepfun")].models["step-custom"] | ||
| const custom = providers[ProviderID.make("custom-provider")].models["custom-step"] | ||
|
|
||
| expect(stepfun.capabilities.interleaved).toEqual({ | ||
| field: "reasoning_content", | ||
| }) | ||
| expect(custom.capabilities.interleaved).toEqual({ | ||
| field: "reasoning_content", | ||
| }) | ||
| }), | ||
| { | ||
| config: { | ||
| provider: { | ||
| stepfun: { | ||
| name: "StepFun", | ||
| npm: "@ai-sdk/openai-compatible", | ||
| api: "https://api.stepfun.com/v1", | ||
| models: { | ||
| "step-custom": { name: "Step Custom" }, | ||
| }, | ||
| options: { apiKey: "test-key" }, | ||
| }, | ||
| "custom-provider": { | ||
| name: "Custom Provider", | ||
| npm: "@ai-sdk/openai-compatible", | ||
| api: "https://api.custom.com/v1", | ||
| models: { | ||
| "custom-step": { id: "step-3.5-flash", name: "Custom Alias" }, | ||
| }, | ||
| options: { apiKey: "test-key" }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WARNING: Kilo-specific logic re-inlined into a shared upstream file instead of the kilocode helper
The first commit on this PR extracted this check into
defaultInterleaved()insrc/kilocode/provider/provider.tsand called it from here behind a singlekilocode_changeimport. The follow-up commit removed that helper and duplicated the same "openai-compatible npm + id contains step/deepseek" condition inline in two places in this shared upstream file (here and in thelayermerge path below), with nokilocode_changemarker.Per the Fork Isolation Rule in
packages/opencode/AGENTS.md, Kilo-specific logic touching a shared upstream file should live insrc/kilocode/<same/path>.tsand be called from a single marked line, to minimize upstream merge-conflict surface. Restoring the extracted helper (as the first commit had it) would avoid duplicating this logic and keep the diff against upstreamprovider.tsminimal.Reply with
@kilocode-bot fix itto have Kilo Code address this issue.