From a5b87fafa37f223877d07cfba2882b298a25f742 Mon Sep 17 00:00:00 2001 From: chrarnoldus <12196001+chrarnoldus@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:13:52 +0000 Subject: [PATCH 1/8] feat(vscode): support custom reasoning options Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .changeset/calm-models-reason.md | 5 +++ .../kilo-vscode/src/shared/custom-provider.ts | 28 ++++++++----- .../custom-provider-dialog-validate.test.ts | 24 +++++++++++- .../tests/unit/custom-provider.test.ts | 36 +++++++++++++++-- .../settings/CustomProviderDialog.tsx | 5 +++ .../settings/CustomProviderModelCard.tsx | 39 ++++++++++++++++++- .../settings/CustomProviderValidation.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/en.ts | 5 +++ 8 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 .changeset/calm-models-reason.md diff --git a/.changeset/calm-models-reason.md b/.changeset/calm-models-reason.md new file mode 100644 index 000000000000..67c0806061bb --- /dev/null +++ b/.changeset/calm-models-reason.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Support adaptive thinking and split reasoning variants for custom OpenAI-compatible providers. diff --git a/packages/kilo-vscode/src/shared/custom-provider.ts b/packages/kilo-vscode/src/shared/custom-provider.ts index b8ed2dfa294c..a359a84d70db 100644 --- a/packages/kilo-vscode/src/shared/custom-provider.ts +++ b/packages/kilo-vscode/src/shared/custom-provider.ts @@ -13,7 +13,8 @@ export const EnvSchema = z const VariantConfigSchema = z.object({ enable_thinking: z.boolean().optional(), - thinking: z.object({ type: z.enum(["enabled", "disabled"]) }).optional(), + thinking: z.object({ type: z.enum(["enabled", "disabled", "adaptive"]) }).optional(), + reasoning_split: z.boolean().optional(), reasoningEffort: z.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).optional(), chat_template_args: z.object({ enable_thinking: z.boolean() }).optional(), }) @@ -155,10 +156,10 @@ function isRecord(v: unknown): v is AnyRecord { } /** - * Build a provider patch that includes null sentinels for models and variants - * that existed in the previous config but are absent from the new one. The CLI - * `config.update` endpoint deep-merges the payload with the existing config; - * without explicit nulls, removed entries would persist on disk. + * Build a provider patch that includes null sentinels for models, variants, and + * variant options that existed in the previous config but are absent from the + * new one. The CLI `config.update` endpoint deep-merges the payload with the + * existing config; without explicit nulls, removed entries would persist on disk. */ export function withCustomProviderDeletions(existing: unknown, next: SanitizedProviderConfig): SanitizedProviderConfig { if (!isRecord(existing)) return next @@ -175,10 +176,19 @@ export function withCustomProviderDeletions(existing: unknown, next: SanitizedPr const newModel = patched[id] if (!isRecord(newModel)) continue const newVariants = isRecord(newModel.variants) ? newModel.variants : {} - const removedVariants = Object.keys(oldVariants).filter((v) => !(v in newVariants)) - if (removedVariants.length === 0) continue - const nulls = Object.fromEntries(removedVariants.map((v) => [v, null])) - patched[id] = { ...newModel, variants: { ...newVariants, ...nulls } } + const changes = Object.fromEntries( + Object.entries(oldVariants).flatMap(([name, oldVariant]) => { + if (!(name in newVariants)) return [[name, null]] + const newVariant = newVariants[name] + if (!isRecord(oldVariant) || !isRecord(newVariant)) return [] + const removed = Object.keys(oldVariant).filter((key) => !(key in newVariant)) + if (removed.length === 0) return [] + const nulls = Object.fromEntries(removed.map((key) => [key, null])) + return [[name, { ...newVariant, ...nulls }]] + }), + ) + if (Object.keys(changes).length === 0) continue + patched[id] = { ...newModel, variants: { ...newVariants, ...changes } } } return { ...next, models: patched as SanitizedProviderConfig["models"] } diff --git a/packages/kilo-vscode/tests/unit/custom-provider-dialog-validate.test.ts b/packages/kilo-vscode/tests/unit/custom-provider-dialog-validate.test.ts index a045e2e1f763..d44f19ece1ad 100644 --- a/packages/kilo-vscode/tests/unit/custom-provider-dialog-validate.test.ts +++ b/packages/kilo-vscode/tests/unit/custom-provider-dialog-validate.test.ts @@ -56,6 +56,7 @@ describe("validateCustomProvider – variant name validation", () => { name: "fast", enableThinking: undefined, thinking: undefined, + splitReasoning: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -73,6 +74,7 @@ describe("validateCustomProvider – variant name validation", () => { name: "", enableThinking: undefined, thinking: undefined, + splitReasoning: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -90,6 +92,7 @@ describe("validateCustomProvider – variant name validation", () => { name: " ", enableThinking: undefined, thinking: undefined, + splitReasoning: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -107,6 +110,7 @@ describe("validateCustomProvider – variant name validation", () => { name: "fast", enableThinking: undefined, thinking: undefined, + splitReasoning: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -114,6 +118,7 @@ describe("validateCustomProvider – variant name validation", () => { name: "fast", enableThinking: undefined, thinking: undefined, + splitReasoning: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -131,6 +136,7 @@ describe("validateCustomProvider – variant name validation", () => { name: "", enableThinking: undefined, thinking: undefined, + splitReasoning: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -148,11 +154,25 @@ describe("validateCustomProvider – variant name validation", () => { const form = base() form.models[0].reasoning = true form.models[0].variants = [ - { name: "eco", enableThinking: true, thinking: undefined, reasoningEffort: "low", chatTemplateArgs: undefined }, + { + name: "eco", + enableThinking: true, + thinking: "adaptive", + splitReasoning: false, + reasoningEffort: "low", + chatTemplateArgs: undefined, + }, ] const out = validateCustomProvider(args(form)) expect(out.result).toBeDefined() const saved = out.result!.config.models["model-1"] as Record - expect(saved.variants).toEqual({ eco: { enable_thinking: true, reasoningEffort: "low" } }) + expect(saved.variants).toEqual({ + eco: { + enable_thinking: true, + thinking: { type: "adaptive" }, + reasoning_split: false, + reasoningEffort: "low", + }, + }) }) }) diff --git a/packages/kilo-vscode/tests/unit/custom-provider.test.ts b/packages/kilo-vscode/tests/unit/custom-provider.test.ts index 1a3ce2c242c5..8df66204540b 100644 --- a/packages/kilo-vscode/tests/unit/custom-provider.test.ts +++ b/packages/kilo-vscode/tests/unit/custom-provider.test.ts @@ -100,7 +100,7 @@ describe("sanitizeCustomProviderConfig", () => { }) }) - it("accepts models with chat_template_args variant", () => { + it("accepts supported thinking variant options", () => { const result = sanitizeCustomProviderConfig({ name: "Thinking Provider", options: { baseURL: "https://example.com/v1" }, @@ -108,7 +108,11 @@ describe("sanitizeCustomProviderConfig", () => { "model-1": { name: "Model One", variants: { - thinking: { chat_template_args: { enable_thinking: true } }, + thinking: { + thinking: { type: "adaptive" }, + reasoning_split: true, + chat_template_args: { enable_thinking: true }, + }, }, }, }, @@ -123,7 +127,11 @@ describe("sanitizeCustomProviderConfig", () => { "model-1": { name: "Model One", variants: { - thinking: { chat_template_args: { enable_thinking: true } }, + thinking: { + thinking: { type: "adaptive" }, + reasoning_split: true, + chat_template_args: { enable_thinking: true }, + }, }, }, }, @@ -187,6 +195,28 @@ describe("withCustomProviderDeletions", () => { expect(model.variants.low).toBeNull() }) + it("emits null for options removed from a surviving variant", () => { + const existing = { + models: { + keep: { + name: "Keep", + variants: { + thinking: { thinking: { type: "adaptive" }, reasoning_split: true, reasoningEffort: "high" }, + }, + }, + }, + } + const next = { + ...baseNext, + models: { + keep: { name: "Keep", variants: { thinking: { reasoningEffort: "high" } } }, + }, + } as typeof baseNext + const result = withCustomProviderDeletions(existing, next) + const model = (result.models as Record }>).keep + expect(model.variants.thinking).toEqual({ reasoningEffort: "high", thinking: null, reasoning_split: null }) + }) + it("does not touch variants on a model that is being deleted", () => { const existing = { models: { gone: { name: "Gone", variants: { a: {} } } } } const result = withCustomProviderDeletions(existing, baseNext) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx index 9b24cacf22eb..0010925c9dd6 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx @@ -77,6 +77,7 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => { typeof vcfg.thinking === "object" && vcfg.thinking !== null ? ((vcfg.thinking as { type?: string }).type as ThinkingTypeValue) : undefined, + splitReasoning: typeof vcfg.reasoning_split === "boolean" ? vcfg.reasoning_split : undefined, reasoningEffort: typeof vcfg.reasoningEffort === "string" ? (vcfg.reasoningEffort as ReasoningEffortValue) : undefined, chatTemplateArgs: @@ -354,6 +355,7 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => { name: "", enableThinking: undefined, thinking: undefined, + splitReasoning: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, } @@ -551,6 +553,9 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => { setForm("models", i(), "variants", vi, "enableThinking", val) } onChangeVariantThinking={(vi, val) => setForm("models", i(), "variants", vi, "thinking", val)} + onChangeVariantSplitReasoning={(vi, val) => + setForm("models", i(), "variants", vi, "splitReasoning", val) + } onChangeVariantReasoningEffort={(vi, val) => setForm("models", i(), "variants", vi, "reasoningEffort", val) } diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderModelCard.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderModelCard.tsx index 29d606ed8bc9..90147e919045 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderModelCard.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderModelCard.tsx @@ -9,7 +9,8 @@ export type Translator = ReturnType["t"] // undefined = not set; true/false = enable_thinking value export type EnableThinkingValue = undefined | boolean -export type ThinkingTypeValue = undefined | "enabled" | "disabled" +export type ThinkingTypeValue = undefined | "enabled" | "disabled" | "adaptive" +export type SplitReasoningValue = undefined | boolean export type ReasoningEffortValue = undefined | "none" | "minimal" | "low" | "medium" | "high" | "xhigh" export type ChatTemplateArgsValue = undefined | boolean @@ -17,6 +18,7 @@ export type VariantEntry = { name: string enableThinking: EnableThinkingValue thinking: ThinkingTypeValue + splitReasoning: SplitReasoningValue reasoningEffort: ReasoningEffortValue chatTemplateArgs: ChatTemplateArgsValue } @@ -40,6 +42,13 @@ const THINKING_OPTIONS: SelectOption[] = [ { value: undefined, labelKey: "provider.custom.models.variants.option.unset" }, { value: "enabled", labelKey: "provider.custom.models.variants.thinking.enabled" }, { value: "disabled", labelKey: "provider.custom.models.variants.thinking.disabled" }, + { value: "adaptive", labelKey: "provider.custom.models.variants.thinking.adaptive" }, +] + +const SPLIT_REASONING_OPTIONS: SelectOption[] = [ + { value: undefined, labelKey: "provider.custom.models.variants.option.unset" }, + { value: true, labelKey: "provider.custom.models.variants.splitReasoning.true" }, + { value: false, labelKey: "provider.custom.models.variants.splitReasoning.false" }, ] const CHAT_TEMPLATE_ARGS_OPTIONS: SelectOption[] = [ @@ -67,6 +76,7 @@ type VariantRowProps = { onChangeName: (val: string) => void onChangeEnableThinking: (val: EnableThinkingValue) => void onChangeThinking: (val: ThinkingTypeValue) => void + onChangeSplitReasoning: (val: SplitReasoningValue) => void onChangeReasoningEffort: (val: ReasoningEffortValue) => void onChangeChatTemplateArgs: (val: ChatTemplateArgsValue) => void onRemove: () => void @@ -152,6 +162,31 @@ function VariantRow(props: VariantRowProps) { triggerVariant="settings" /> +
+ + option.value === form.npm)} + value={(option) => option.value} + label={(option) => option.label} + onSelect={(option) => { + if (!option) return + setForm("npm", option.value) + setFetchPackage(option.value) + }} + variant="secondary" + triggerVariant="settings" + /> +
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderValidation.ts b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderValidation.ts index 35ca1a6c7e3f..c7db59ffee85 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderValidation.ts +++ b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderValidation.ts @@ -1,3 +1,4 @@ +import type { CustomProviderPackage } from "../../../../src/shared/provider-model" import type { ModelEntry, VariantEntry } from "./CustomProviderModelCard" type Translator = (key: string, params?: Record) => string @@ -10,6 +11,7 @@ export type HeaderRow = { export type FormState = { providerID: string name: string + npm: CustomProviderPackage baseURL: string apiKey: string models: ModelEntry[] @@ -42,7 +44,7 @@ type ValidateResult = { name: string key: string | undefined config: { - npm: string + npm: CustomProviderPackage name: string env?: string[] options: { baseURL: string; headers?: Record } @@ -52,7 +54,6 @@ type ValidateResult = { } const PROVIDER_ID = /^[a-z0-9][a-z0-9-_]*$/ -const OPENAI_COMPATIBLE = "@ai-sdk/openai-compatible" function checkVariant(v: VariantEntry, seen: Set, t: Translator) { const n = v.name.trim() @@ -190,7 +191,7 @@ export function validateCustomProvider(input: ValidateArgs): ValidateResult { name, key, config: { - npm: OPENAI_COMPATIBLE, + npm: input.form.npm, name, ...resolveEnv(rawEnv, savedEnv), options, diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx index f8b143116016..c3efad52525e 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx @@ -17,7 +17,7 @@ import ProviderConnectDialog from "./ProviderConnectDialog" import ProviderSelectDialog from "./ProviderSelectDialog" import { CUSTOM_PROVIDER_ID, isPopularProvider, providerIcon, providerNoteKey, sortProviders } from "./provider-catalog" import { disabledProviderOptions, providersWithKiloFallback, visibleConnectedIds } from "./provider-visibility" -import { KILO_PROVIDER_ID, CUSTOM_PROVIDER_PACKAGE } from "../../../../src/shared/provider-model" +import { isCustomProviderPackage, KILO_PROVIDER_ID } from "../../../../src/shared/provider-model" import { createProviderAction } from "../../utils/provider-action" type ProviderSource = "env" | "api" | "config" | "custom" @@ -79,7 +79,7 @@ const ProvidersTab: Component = () => { if (current === "api") return language.t("provider.connect.method.apiKey") if (current === "config") { const cfg = config().provider?.[item.id] - if (cfg?.npm === "@ai-sdk/openai-compatible") return language.t("settings.providers.tag.custom") + if (isCustomProviderPackage(cfg?.npm)) return language.t("settings.providers.tag.custom") return language.t("settings.providers.tag.config") } if (item.id === "openai" && current === "custom") return language.t("settings.providers.tag.chatgpt") @@ -93,7 +93,7 @@ const ProvidersTab: Component = () => { function isCustom(item: Provider) { const cfg = config().provider?.[item.id] - return cfg?.npm === CUSTOM_PROVIDER_PACKAGE + return isCustomProviderPackage(cfg?.npm) } function editProvider(item: Provider) { diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index b329c8e0fca2..aab8d5b1426f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -812,6 +812,7 @@ export const dict = { "provider.custom.field.providerID.description": "Lowercase letters, numbers, hyphens, or underscores", "provider.custom.field.name.label": "Display name", "provider.custom.field.name.placeholder": "My AI Provider", + "provider.custom.field.package.label": "Provider API", "provider.custom.field.baseURL.label": "Base URL", "provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1", "provider.custom.field.apiKey.label": "API key", From 3ffb1ad64ac719da59a8df75edef18ee5392f2b8 Mon Sep 17 00:00:00 2001 From: chrarnoldus <12196001+chrarnoldus@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:28:25 +0000 Subject: [PATCH 3/8] fix(vscode): avoid variant tuple inference Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../kilo-vscode/src/shared/custom-provider.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/kilo-vscode/src/shared/custom-provider.ts b/packages/kilo-vscode/src/shared/custom-provider.ts index a3aa335d14da..2980f9dbd042 100644 --- a/packages/kilo-vscode/src/shared/custom-provider.ts +++ b/packages/kilo-vscode/src/shared/custom-provider.ts @@ -178,17 +178,19 @@ export function withCustomProviderDeletions(existing: unknown, next: SanitizedPr const model = oldModel.reasoning === true && !("reasoning" in newModel) ? { ...newModel, reasoning: null } : newModel const oldVariants = isRecord(oldModel.variants) ? oldModel.variants : {} const newVariants = isRecord(model.variants) ? model.variants : {} - const changes = Object.fromEntries( - Object.entries(oldVariants).flatMap(([name, oldVariant]) => { - if (!(name in newVariants)) return [[name, null]] - const newVariant = newVariants[name] - if (!isRecord(oldVariant) || !isRecord(newVariant)) return [] - const removed = Object.keys(oldVariant).filter((key) => !(key in newVariant)) - if (removed.length === 0) return [] - const nulls = Object.fromEntries(removed.map((key) => [key, null])) - return [[name, { ...newVariant, ...nulls }]] - }), - ) + const changes: AnyRecord = {} + for (const [name, oldVariant] of Object.entries(oldVariants)) { + if (!(name in newVariants)) { + changes[name] = null + continue + } + const newVariant = newVariants[name] + if (!isRecord(oldVariant) || !isRecord(newVariant)) continue + const removed = Object.keys(oldVariant).filter((key) => !(key in newVariant)) + if (removed.length === 0) continue + const nulls = Object.fromEntries(removed.map((key) => [key, null])) + changes[name] = { ...newVariant, ...nulls } + } if (Object.keys(changes).length === 0) { patched[id] = model continue From a65f780fdd552a0024aa70f73756fe9ba8a7a5f8 Mon Sep 17 00:00:00 2001 From: chrarnoldus <12196001+chrarnoldus@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:19:01 +0000 Subject: [PATCH 4/8] feat(vscode): configure provider output effort Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .changeset/calm-models-reason.md | 2 +- .../kilo-vscode/src/shared/custom-provider.ts | 1 + .../custom-provider-dialog-validate.test.ts | 8 ++++ .../tests/unit/custom-provider.test.ts | 1 + .../settings/CustomProviderDialog.tsx | 6 +++ .../settings/CustomProviderModelCard.tsx | 39 +++++++++++++++++++ .../settings/CustomProviderValidation.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/en.ts | 7 ++++ 8 files changed, 64 insertions(+), 1 deletion(-) diff --git a/.changeset/calm-models-reason.md b/.changeset/calm-models-reason.md index 743068c124c5..49fc92940fe6 100644 --- a/.changeset/calm-models-reason.md +++ b/.changeset/calm-models-reason.md @@ -2,4 +2,4 @@ "kilo-code": minor --- -Support multiple provider APIs, adaptive thinking, and split reasoning variants for custom providers. +Support multiple provider APIs, adaptive thinking, split reasoning, and output effort variants for custom providers. diff --git a/packages/kilo-vscode/src/shared/custom-provider.ts b/packages/kilo-vscode/src/shared/custom-provider.ts index 39c54bbd00b5..8cadf7d07a99 100644 --- a/packages/kilo-vscode/src/shared/custom-provider.ts +++ b/packages/kilo-vscode/src/shared/custom-provider.ts @@ -17,6 +17,7 @@ const VariantConfigSchema = z.object({ thinking: z.object({ type: z.enum(["enabled", "disabled", "adaptive"]) }).optional(), reasoning_split: z.boolean().optional(), reasoningEffort: z.enum(["none", "minimal", "low", "medium", "high", "xhigh"]).optional(), + effort: z.enum(["low", "medium", "high", "xhigh", "max"]).optional(), chat_template_args: z.object({ enable_thinking: z.boolean() }).optional(), }) diff --git a/packages/kilo-vscode/tests/unit/custom-provider-dialog-validate.test.ts b/packages/kilo-vscode/tests/unit/custom-provider-dialog-validate.test.ts index 58ccb989a52f..0ba8b3f4e94b 100644 --- a/packages/kilo-vscode/tests/unit/custom-provider-dialog-validate.test.ts +++ b/packages/kilo-vscode/tests/unit/custom-provider-dialog-validate.test.ts @@ -65,6 +65,7 @@ describe("validateCustomProvider – variant name validation", () => { enableThinking: undefined, thinking: undefined, splitReasoning: undefined, + outputEffort: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -83,6 +84,7 @@ describe("validateCustomProvider – variant name validation", () => { enableThinking: undefined, thinking: undefined, splitReasoning: undefined, + outputEffort: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -101,6 +103,7 @@ describe("validateCustomProvider – variant name validation", () => { enableThinking: undefined, thinking: undefined, splitReasoning: undefined, + outputEffort: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -119,6 +122,7 @@ describe("validateCustomProvider – variant name validation", () => { enableThinking: undefined, thinking: undefined, splitReasoning: undefined, + outputEffort: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -127,6 +131,7 @@ describe("validateCustomProvider – variant name validation", () => { enableThinking: undefined, thinking: undefined, splitReasoning: undefined, + outputEffort: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -145,6 +150,7 @@ describe("validateCustomProvider – variant name validation", () => { enableThinking: undefined, thinking: undefined, splitReasoning: undefined, + outputEffort: undefined, reasoningEffort: undefined, chatTemplateArgs: undefined, }, @@ -167,6 +173,7 @@ describe("validateCustomProvider – variant name validation", () => { enableThinking: true, thinking: "adaptive", splitReasoning: false, + outputEffort: "max", reasoningEffort: "low", chatTemplateArgs: undefined, }, @@ -179,6 +186,7 @@ describe("validateCustomProvider – variant name validation", () => { enable_thinking: true, thinking: { type: "adaptive" }, reasoning_split: false, + effort: "max", reasoningEffort: "low", }, }) diff --git a/packages/kilo-vscode/tests/unit/custom-provider.test.ts b/packages/kilo-vscode/tests/unit/custom-provider.test.ts index 3794588a8345..66a820990a6a 100644 --- a/packages/kilo-vscode/tests/unit/custom-provider.test.ts +++ b/packages/kilo-vscode/tests/unit/custom-provider.test.ts @@ -132,6 +132,7 @@ describe("sanitizeCustomProviderConfig", () => { thinking: { thinking: { type: "adaptive" }, reasoning_split: true, + effort: "max", chat_template_args: { enable_thinking: true }, }, }, diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx index eace0b339bc9..5078467f258f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx @@ -26,6 +26,7 @@ import type { ChatTemplateArgsValue, EnableThinkingValue, ModelEntry, + OutputEffortValue, ReasoningEffortValue, ThinkingTypeValue, VariantEntry, @@ -92,6 +93,7 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => { splitReasoning: typeof vcfg.reasoning_split === "boolean" ? vcfg.reasoning_split : undefined, reasoningEffort: typeof vcfg.reasoningEffort === "string" ? (vcfg.reasoningEffort as ReasoningEffortValue) : undefined, + outputEffort: typeof vcfg.effort === "string" ? (vcfg.effort as OutputEffortValue) : undefined, chatTemplateArgs: typeof vcfg.chat_template_args === "object" && vcfg.chat_template_args !== null ? ((vcfg.chat_template_args as { enable_thinking?: boolean }).enable_thinking as ChatTemplateArgsValue) @@ -373,6 +375,7 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => { thinking: undefined, splitReasoning: undefined, reasoningEffort: undefined, + outputEffort: undefined, chatTemplateArgs: undefined, } setForm("models", mi, "variants", (v) => [...v, blank]) @@ -599,6 +602,9 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => { onChangeVariantReasoningEffort={(vi, val) => setForm("models", i(), "variants", vi, "reasoningEffort", val) } + onChangeVariantOutputEffort={(vi, val) => + setForm("models", i(), "variants", vi, "outputEffort", val) + } onChangeVariantChatTemplateArgs={(vi, val) => setForm("models", i(), "variants", vi, "chatTemplateArgs", val) } diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderModelCard.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderModelCard.tsx index 919499380ca7..1af17b3288e5 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderModelCard.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderModelCard.tsx @@ -12,6 +12,7 @@ export type EnableThinkingValue = undefined | boolean export type ThinkingTypeValue = undefined | "enabled" | "disabled" | "adaptive" export type SplitReasoningValue = undefined | boolean export type ReasoningEffortValue = undefined | "none" | "minimal" | "low" | "medium" | "high" | "xhigh" +export type OutputEffortValue = undefined | "low" | "medium" | "high" | "xhigh" | "max" export type ChatTemplateArgsValue = undefined | boolean export type VariantEntry = { @@ -20,6 +21,7 @@ export type VariantEntry = { thinking: ThinkingTypeValue splitReasoning: SplitReasoningValue reasoningEffort: ReasoningEffortValue + outputEffort: OutputEffortValue chatTemplateArgs: ChatTemplateArgsValue } @@ -67,6 +69,15 @@ const REASONING_EFFORT_OPTIONS: SelectOption[] = [ { value: "xhigh", labelKey: "provider.custom.models.variants.reasoningEffort.xhigh" }, ] +const OUTPUT_EFFORT_OPTIONS: SelectOption[] = [ + { value: undefined, labelKey: "provider.custom.models.variants.option.unset" }, + { value: "low", labelKey: "provider.custom.models.variants.outputEffort.low" }, + { value: "medium", labelKey: "provider.custom.models.variants.outputEffort.medium" }, + { value: "high", labelKey: "provider.custom.models.variants.outputEffort.high" }, + { value: "xhigh", labelKey: "provider.custom.models.variants.outputEffort.xhigh" }, + { value: "max", labelKey: "provider.custom.models.variants.outputEffort.max" }, +] + type VariantRowProps = { v: VariantEntry vi: () => number @@ -78,6 +89,7 @@ type VariantRowProps = { onChangeThinking: (val: ThinkingTypeValue) => void onChangeSplitReasoning: (val: SplitReasoningValue) => void onChangeReasoningEffort: (val: ReasoningEffortValue) => void + onChangeOutputEffort: (val: OutputEffortValue) => void onChangeChatTemplateArgs: (val: ChatTemplateArgsValue) => void onRemove: () => void } @@ -212,6 +224,31 @@ function VariantRow(props: VariantRowProps) { triggerVariant="settings" /> +
+ +