diff --git a/bun.lock b/bun.lock index 208488bdd0a..d6b1a622f8c 100644 --- a/bun.lock +++ b/bun.lock @@ -24,7 +24,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@kilocode/kilo-i18n": "workspace:*", "@kilocode/sdk": "workspace:*", @@ -75,7 +75,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -109,7 +109,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -136,7 +136,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -160,7 +160,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -185,7 +185,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -216,7 +216,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -245,7 +245,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -261,7 +261,7 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@clack/prompts": "1.0.0-alpha.1", "@kilocode/plugin": "workspace:*", @@ -303,7 +303,7 @@ }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", "@opentelemetry/api": "1.9.0", @@ -323,7 +323,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "1.0.13", + "version": "1.0.14", "bin": { "opencode": "./bin/opencode", }, @@ -429,7 +429,7 @@ }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@kilocode/sdk": "workspace:*", "zod": "catalog:", @@ -450,7 +450,7 @@ }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "1.0.13", + "version": "1.0.14", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -461,7 +461,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@kilocode/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -474,7 +474,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", @@ -516,7 +516,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "zod": "catalog:", }, @@ -527,7 +527,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/flake.nix b/flake.nix index f1e24bed5e6..0fa41f1294c 100644 --- a/flake.nix +++ b/flake.nix @@ -33,10 +33,14 @@ pkg-config openssl git + gh + playwright-driver.browsers kilo ]; shellHook = '' export KILO_ROOT="$PWD" + export PLAYWRIGHT_BROWSERS_PATH="${pkgs.playwright-driver.browsers}" + export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true ''; }; }); diff --git a/packages/app/package.json b/packages/app/package.json index febb800d7de..365878e952b 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.0.13", + "version": "1.0.14", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 0f8379a4c02..09b4014047e 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.0.13", + "version": "1.0.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index eb967c48839..88b41943612 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.0.13", + "version": "1.0.14", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 4d673c07091..7eca3c93f6d 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.0.13", + "version": "1.0.14", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index fe58a235868..c2f4f692c96 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.0.13", + "version": "1.0.14", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 8e3143a6fc2..4163b86f232 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.0.13", + "version": "1.0.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 3af8040468c..138fe57bc03 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.0.13", + "version": "1.0.14", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 32a30df0cb2..72e80bf8cd8 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "kilo" name = "Kilo" description = "The open source coding agent." -version = "1.1.43" +version = "1.0.14" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/Kilo-Org/kilo" @@ -11,26 +11,26 @@ name = "Kilo" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.43/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.0.14/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.43/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.0.14/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.43/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.0.14/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.43/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.0.14/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.43/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.0.14/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index b58b40e9ad8..2c97f493d6d 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.0.13", + "version": "1.0.14", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json index 1d0dbfc5eb8..57266bd3aad 100644 --- a/packages/kilo-gateway/package.json +++ b/packages/kilo-gateway/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-gateway", - "version": "1.0.13", + "version": "1.0.14", "type": "module", "license": "MIT", "description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration", diff --git a/packages/kilo-telemetry/package.json b/packages/kilo-telemetry/package.json index 8534864dc1d..72890bbdd8b 100644 --- a/packages/kilo-telemetry/package.json +++ b/packages/kilo-telemetry/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-telemetry", - "version": "1.0.13", + "version": "1.0.14", "type": "module", "license": "MIT", "description": "Telemetry for Kilo CLI - PostHog analytics integration", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index a4a8ca31582..ae99f27b623 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.0.13", + "version": "1.0.14", "name": "@kilocode/cli", "type": "module", "license": "MIT", diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index cf402ee7d1a..7c6f63e56c3 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -34,6 +34,7 @@ export namespace ProviderTransform { case "@ai-sdk/gateway": return "gateway" case "@openrouter/ai-sdk-provider": + case "@kilocode/kilo-gateway": // kilocode_change return "openrouter" } return undefined @@ -66,7 +67,14 @@ export namespace ProviderTransform { .filter((msg): msg is ModelMessage => msg !== undefined && msg.content !== "") } - if (model.api.id.includes("claude")) { + // kilocode_change - skip toolCallId normalization for OpenRouter/Kilo Gateway + // OpenRouter handles tool call IDs differently and modifying messages can break + // thinking/redacted_thinking blocks which must remain unchanged per Anthropic API + if ( + model.api.id.includes("claude") && + model.api.npm !== "@openrouter/ai-sdk-provider" && + model.api.npm !== "@kilocode/kilo-gateway" + ) { return msgs.map((msg) => { if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) { msg.content = msg.content.map((part) => { @@ -239,13 +247,89 @@ export namespace ProviderTransform { export function message(msgs: ModelMessage[], model: Provider.Model, options: Record) { msgs = unsupportedParts(msgs, model) msgs = normalizeMessages(msgs, model, options) + + // kilocode_change - skip caching for OpenRouter/Kilo Gateway to avoid modifying thinking blocks + // Anthropic API requires thinking/redacted_thinking blocks to remain exactly unchanged + const isOpenRouterOrKilo = + model.api.npm === "@openrouter/ai-sdk-provider" || model.api.npm === "@kilocode/kilo-gateway" + + // kilocode_change - strip thinking/reasoning blocks for OpenRouter/Kilo Gateway + // Anthropic's API requires thinking blocks to be EXACTLY unchanged, but our storage + // reconstructs them which counts as modification. Stripping them is safe because + // the reasoning was already shown to the user and doesn't need to be sent back. + if (isOpenRouterOrKilo) { + // Helper to strip reasoning-related data from provider options/metadata + const stripReasoningData = (opts: Record | undefined) => { + if (!opts) return undefined // Return undefined instead of empty object to clean up + const result = { ...opts } + // Strip from openrouter namespace + if (result.openrouter) { + result.openrouter = { ...result.openrouter } + delete result.openrouter.reasoning_details + delete result.openrouter.reasoning + delete result.openrouter.thinking + } + // Strip from kilo namespace + if (result.kilo) { + result.kilo = { ...result.kilo } + delete result.kilo.reasoning_details + delete result.kilo.reasoning + delete result.kilo.thinking + } + // Strip from anthropic namespace + if (result.anthropic) { + result.anthropic = { ...result.anthropic } + delete result.anthropic.thinking + delete result.anthropic.reasoning + } + return result + } + + msgs = msgs.flatMap((msg): ModelMessage[] => { + // Handle string content (just strip metadata) + if (!Array.isArray(msg.content)) { + const result = { ...msg, providerOptions: stripReasoningData(msg.providerOptions) } + if ("experimental_providerMetadata" in msg) { + ;(result as any).experimental_providerMetadata = stripReasoningData( + (msg as any).experimental_providerMetadata, + ) + } + return [result] + } + // Filter out reasoning parts from content and strip metadata from remaining parts + const filtered = msg.content + .filter( + (part: any) => part.type !== "thinking" && part.type !== "redacted_thinking" && part.type !== "reasoning", + ) + .map((part: any) => ({ + ...part, + providerOptions: stripReasoningData(part.providerOptions), + providerMetadata: stripReasoningData(part.providerMetadata), + experimental_providerMetadata: stripReasoningData(part.experimental_providerMetadata), + })) + + // Providers may reject empty array content; drop empty messages. + if (filtered.length === 0) return [] + + // Also strip from message-level options/metadata + const result = { ...msg, content: filtered, providerOptions: stripReasoningData(msg.providerOptions) } + if ("experimental_providerMetadata" in msg) { + ;(result as any).experimental_providerMetadata = stripReasoningData( + (msg as any).experimental_providerMetadata, + ) + } + return [result as ModelMessage] + }) + } + if ( - model.providerID === "anthropic" || - model.api.id.includes("anthropic") || - model.api.id.includes("claude") || - model.id.includes("anthropic") || - model.id.includes("claude") || - model.api.npm === "@ai-sdk/anthropic" + !isOpenRouterOrKilo && + (model.providerID === "anthropic" || + model.api.id.includes("anthropic") || + model.api.id.includes("claude") || + model.id.includes("anthropic") || + model.id.includes("claude") || + model.api.npm === "@ai-sdk/anthropic") ) { msgs = applyCaching(msgs, model.providerID) } @@ -330,7 +414,8 @@ export namespace ProviderTransform { // see: https://docs.x.ai/docs/guides/reasoning#control-how-hard-the-model-thinks if (id.includes("grok") && id.includes("grok-3-mini")) { - if (model.api.npm === "@openrouter/ai-sdk-provider") { + if (model.api.npm === "@openrouter/ai-sdk-provider" || model.api.npm === "@kilocode/kilo-gateway") { + // kilocode_change - add Kilo Gateway support return { low: { reasoning: { effort: "low" } }, high: { reasoning: { effort: "high" } }, @@ -348,8 +433,19 @@ export namespace ProviderTransform { if (!model.id.includes("gpt") && !model.id.includes("gemini-3")) return {} return Object.fromEntries(OPENAI_EFFORTS.map((effort) => [effort, { reasoning: { effort } }])) - // kilocode_change start - GPT models via Kilo need encrypted reasoning content to avoid org_id mismatch + // kilocode_change start - Claude and GPT models via Kilo Gateway case "@kilocode/kilo-gateway": + // Claude/Anthropic models support reasoning via effort levels through OpenRouter API + // OpenRouter maps effort to budget_tokens percentages (xhigh=95%, high=80%, medium=50%, low=20%, minimal=10%) + if ( + model.id.includes("claude") || + model.id.includes("anthropic") || + model.api.id.includes("claude") || + model.api.id.includes("anthropic") + ) { + return Object.fromEntries(OPENAI_EFFORTS.map((effort) => [effort, { reasoning: { effort } }])) + } + // GPT models via Kilo need encrypted reasoning content to avoid org_id mismatch if (!model.id.includes("gpt") && !model.id.includes("gemini-3")) return {} return Object.fromEntries( OPENAI_EFFORTS.map((effort) => [ @@ -576,6 +672,16 @@ export namespace ProviderTransform { if (input.model.api.id.includes("gemini-3")) { result["reasoning"] = { effort: "high" } } + // kilocode_change - enable reasoning for Claude models via Kilo Gateway + if ( + input.model.api.npm === "@kilocode/kilo-gateway" && + (input.model.id.includes("claude") || + input.model.id.includes("anthropic") || + input.model.api.id.includes("claude") || + input.model.api.id.includes("anthropic")) + ) { + result["reasoning"] = { effort: "medium" } + } } if ( @@ -647,10 +753,21 @@ export namespace ProviderTransform { } return { thinkingConfig: { thinkingBudget: 0 } } } - if (model.providerID === "openrouter") { + if (model.providerID === "openrouter" || model.api.npm === "@kilocode/kilo-gateway") { + // kilocode_change - add Kilo Gateway support with model-specific handling if (model.api.id.includes("google")) { return { reasoning: { enabled: false } } } + // Claude models need reasoning.effort format (OpenRouter API) + if ( + model.id.includes("claude") || + model.id.includes("anthropic") || + model.api.id.includes("claude") || + model.api.id.includes("anthropic") + ) { + return { reasoning: { effort: "minimal" } } + } + // Other models use reasoningEffort (AI SDK format) return { reasoningEffort: "minimal" } } return {} diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 1d69a2a2951..4ab80ce764a 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -623,6 +623,75 @@ describe("ProviderTransform.message - anthropic empty content filtering", () => }) }) +describe("ProviderTransform.message - OpenRouter/Kilo Gateway thinking stripping", () => { + const claudeViaGateway = { + id: "openrouter/anthropic/claude-3-5-sonnet", + providerID: "openrouter", + api: { + id: "anthropic/claude-3-5-sonnet", + url: "https://openrouter.ai", + npm: "@kilocode/kilo-gateway", + }, + name: "Claude 3.5 Sonnet (Gateway)", + capabilities: { + temperature: true, + reasoning: true, + attachment: true, + toolcall: true, + input: { text: true, audio: false, image: true, video: false, pdf: true }, + output: { text: true, audio: false, image: false, video: false, pdf: false }, + interleaved: false, + }, + cost: { + input: 0, + output: 0, + cache: { read: 0, write: 0 }, + }, + limit: { + context: 200000, + output: 8192, + }, + status: "active", + options: {}, + headers: {}, + } as any + + test("drops messages that become empty after stripping thinking/reasoning", () => { + const msgs = [ + { role: "user", content: "Hello" }, + { + role: "assistant", + content: [{ type: "thinking", text: "..." }, { type: "reasoning", text: "..." }, { type: "redacted_thinking" }], + }, + { role: "user", content: "World" }, + ] as any[] + + const result = ProviderTransform.message(msgs, claudeViaGateway, {}) + + expect(result).toHaveLength(2) + expect(result[0].content).toBe("Hello") + expect(result[1].content).toBe("World") + }) + + test("keeps messages that still contain supported parts after stripping", () => { + const msgs = [ + { + role: "assistant", + content: [ + { type: "thinking", text: "..." }, + { type: "text", text: "Answer" }, + ], + }, + ] as any[] + + const result = ProviderTransform.message(msgs, claudeViaGateway, {}) + + expect(result).toHaveLength(1) + expect(result[0].content).toHaveLength(1) + expect(result[0].content[0]).toEqual({ type: "text", text: "Answer" }) + }) +}) + describe("ProviderTransform.message - strip openai metadata when store=false", () => { const openaiModel = { id: "openai/gpt-5", @@ -1031,41 +1100,42 @@ describe("ProviderTransform.message - claude w/bedrock custom inference profile" }) }) -describe("ProviderTransform.variants", () => { - const createMockModel = (overrides: Partial = {}): any => ({ - id: "test/test-model", - providerID: "test", - api: { - id: "test-model", - url: "https://api.test.com", - npm: "@ai-sdk/openai", - }, - name: "Test Model", - capabilities: { - temperature: true, - reasoning: true, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: false }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { - input: 0.001, - output: 0.002, - cache: { read: 0.0001, write: 0.0002 }, - }, - limit: { - context: 200_000, - output: 64_000, - }, - status: "active", - options: {}, - headers: {}, - release_date: "2024-01-01", - ...overrides, - }) +// Helper function moved to top level for reuse across test suites +const createMockModel = (overrides: Partial = {}): any => ({ + id: "test/test-model", + providerID: "test", + api: { + id: "test-model", + url: "https://api.test.com", + npm: "@ai-sdk/openai", + }, + name: "Test Model", + capabilities: { + temperature: true, + reasoning: true, + attachment: true, + toolcall: true, + input: { text: true, audio: false, image: true, video: false, pdf: false }, + output: { text: true, audio: false, image: false, video: false, pdf: false }, + interleaved: false, + }, + cost: { + input: 0.001, + output: 0.002, + cache: { read: 0.0001, write: 0.0002 }, + }, + limit: { + context: 200_000, + output: 64_000, + }, + status: "active", + options: {}, + headers: {}, + release_date: "2024-01-01", + ...overrides, +}) +describe("ProviderTransform.variants", () => { test("returns empty object when model has no reasoning capabilities", () => { const model = createMockModel({ capabilities: { reasoning: false }, @@ -1206,6 +1276,107 @@ describe("ProviderTransform.variants", () => { }) }) + // kilocode_change start + describe("@kilocode/kilo-gateway", () => { + test("claude models return OPENAI_EFFORTS with reasoning", () => { + const model = createMockModel({ + id: "kilo/anthropic/claude-sonnet-4", + providerID: "kilo", + api: { + id: "anthropic/claude-sonnet-4", + url: "https://gateway.kilo.ai", + npm: "@kilocode/kilo-gateway", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"]) + expect(result.none).toEqual({ reasoning: { effort: "none" } }) + expect(result.low).toEqual({ reasoning: { effort: "low" } }) + expect(result.medium).toEqual({ reasoning: { effort: "medium" } }) + expect(result.high).toEqual({ reasoning: { effort: "high" } }) + expect(result.xhigh).toEqual({ reasoning: { effort: "xhigh" } }) + }) + + test("anthropic models in api.id return OPENAI_EFFORTS with reasoning", () => { + const model = createMockModel({ + id: "kilo/anthropic/claude-opus-4", + providerID: "kilo", + api: { + id: "anthropic/claude-opus-4", + url: "https://gateway.kilo.ai", + npm: "@kilocode/kilo-gateway", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"]) + expect(result.low).toEqual({ reasoning: { effort: "low" } }) + }) + + test("gpt models return OPENAI_EFFORTS with reasoning and encrypted content", () => { + const model = createMockModel({ + id: "kilo/openai/gpt-5", + providerID: "kilo", + api: { + id: "openai/gpt-5", + url: "https://gateway.kilo.ai", + npm: "@kilocode/kilo-gateway", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"]) + expect(result.low).toEqual({ + reasoningEffort: "low", + reasoningSummary: "auto", + include: ["reasoning.encrypted_content"], + }) + }) + + test("gemini-3 models return OPENAI_EFFORTS with reasoning and encrypted content", () => { + const model = createMockModel({ + id: "kilo/google/gemini-3-pro", + providerID: "kilo", + api: { + id: "google/gemini-3-pro", + url: "https://gateway.kilo.ai", + npm: "@kilocode/kilo-gateway", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["none", "minimal", "low", "medium", "high", "xhigh"]) + }) + + test("non-qualifying models return empty object", () => { + const model = createMockModel({ + id: "kilo/meta/llama-4", + providerID: "kilo", + api: { + id: "meta/llama-4", + url: "https://gateway.kilo.ai", + npm: "@kilocode/kilo-gateway", + }, + }) + const result = ProviderTransform.variants(model) + expect(result).toEqual({}) + }) + + test("grok-3-mini returns low and high with reasoning", () => { + const model = createMockModel({ + id: "kilo/x-ai/grok-3-mini", + providerID: "kilo", + api: { + id: "x-ai/grok-3-mini", + url: "https://gateway.kilo.ai", + npm: "@kilocode/kilo-gateway", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "high"]) + expect(result.low).toEqual({ reasoning: { effort: "low" } }) + expect(result.high).toEqual({ reasoning: { effort: "high" } }) + }) + }) + // kilocode_change end + describe("@ai-sdk/gateway", () => { test("returns OPENAI_EFFORTS with reasoningEffort", () => { const model = createMockModel({ @@ -1725,3 +1896,65 @@ describe("ProviderTransform.variants", () => { }) }) }) + +// kilocode_change start +describe("ProviderTransform.smallOptions", () => { + describe("@kilocode/kilo-gateway", () => { + test("claude models use reasoning.effort format", () => { + const model = createMockModel({ + id: "kilo/anthropic/claude-sonnet-4", + providerID: "kilo", + api: { + id: "anthropic/claude-sonnet-4", + url: "https://gateway.kilo.ai", + npm: "@kilocode/kilo-gateway", + }, + }) + const result = ProviderTransform.smallOptions(model) + expect(result).toEqual({ reasoning: { effort: "minimal" } }) + }) + + test("anthropic models in api.id use reasoning.effort format", () => { + const model = createMockModel({ + id: "kilo/anthropic/claude-opus-4", + providerID: "kilo", + api: { + id: "anthropic/claude-opus-4", + url: "https://gateway.kilo.ai", + npm: "@kilocode/kilo-gateway", + }, + }) + const result = ProviderTransform.smallOptions(model) + expect(result).toEqual({ reasoning: { effort: "minimal" } }) + }) + + test("non-claude models use reasoningEffort format", () => { + const model = createMockModel({ + id: "kilo/openai/gpt-4", + providerID: "kilo", + api: { + id: "openai/gpt-4", + url: "https://gateway.kilo.ai", + npm: "@kilocode/kilo-gateway", + }, + }) + const result = ProviderTransform.smallOptions(model) + expect(result).toEqual({ reasoningEffort: "minimal" }) + }) + + test("google models disable reasoning", () => { + const model = createMockModel({ + id: "kilo/google/gemini-2.0-flash", + providerID: "kilo", + api: { + id: "google/gemini-2.0-flash", + url: "https://gateway.kilo.ai", + npm: "@kilocode/kilo-gateway", + }, + }) + const result = ProviderTransform.smallOptions(model) + expect(result).toEqual({ reasoning: { enabled: false } }) + }) + }) +}) +// kilocode_change end diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 4f66901edcc..dd2965c155a 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/plugin", - "version": "1.0.13", + "version": "1.0.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index da645373ba2..4c54b889ba0 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/sdk", - "version": "1.0.13", + "version": "1.0.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/slack/package.json b/packages/slack/package.json index 26794fb5b7f..b063aa26174 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.0.13", + "version": "1.0.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index b8dbc98a307..59e69dfde8c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.0.13", + "version": "1.0.14", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index 38143cd603a..6d2bdf89f0e 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.0.13", + "version": "1.0.14", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index 9f1a7b62a9a..56b63ed2330 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.0.13", + "version": "1.0.14", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index f957c454b7c..1773d17f05c 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.0.13", + "version": "1.0.14", "publisher": "sst-dev", "repository": { "type": "git",