From de6c1fdeae945184f073e3da8245dd8b2529bd65 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Mar 2026 11:03:44 +0100 Subject: [PATCH 1/4] Convert Kilo/OpenRouter variants to OpenAI/Anthropic format if necessary and use generic openaiCompatible name like elsewhere --- packages/kilo-gateway/src/provider.ts | 2 +- .../opencode/src/kilocode/provider-options.ts | 23 +++++++++++++++++++ packages/opencode/src/provider/transform.ts | 10 ++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/src/kilocode/provider-options.ts diff --git a/packages/kilo-gateway/src/provider.ts b/packages/kilo-gateway/src/provider.ts index a075dc5bf3a..618360a4c35 100644 --- a/packages/kilo-gateway/src/provider.ts +++ b/packages/kilo-gateway/src/provider.ts @@ -78,7 +78,7 @@ export function createKilo(options: KiloProviderOptions = {}): KiloProvider { const openrouter = createOpenRouter(sdkOptions) const anthropic = createAnthropic(sdkOptions) const openai = createOpenAI(sdkOptions) - const openaiCompatible = createOpenAICompatible({ ...sdkOptions, name: "kilo.openai-compatible" }) + const openaiCompatible = createOpenAICompatible({ ...sdkOptions, name: "openaiCompatible" }) return { languageModel(modelId) { diff --git a/packages/opencode/src/kilocode/provider-options.ts b/packages/opencode/src/kilocode/provider-options.ts new file mode 100644 index 00000000000..62fbe0de20e --- /dev/null +++ b/packages/opencode/src/kilocode/provider-options.ts @@ -0,0 +1,23 @@ +import type { AnthropicProviderOptions } from "@ai-sdk/anthropic" +import type { OpenAIResponsesProviderOptions } from "@ai-sdk/openai" +import type { OpenRouterProviderOptions } from "@openrouter/ai-sdk-provider" + +export function kiloProviderOptions(options: { [x: string]: any }) { + const result: Record = {} + const openrouter = options as OpenRouterProviderOptions & { + verbosity?: "high" | "medium" | "low" + } + result.openrouter = openrouter + result.openai = { + reasoningEffort: + openrouter.reasoning && "effort" in openrouter.reasoning ? openrouter.reasoning?.effort : undefined, + textVerbosity: openrouter.verbosity, + store: false, + //forceReasoning: openrouter.reasoning?.enabled, // ai sdk v6 + } satisfies OpenAIResponsesProviderOptions + result.anthropic = { + thinking: { type: openrouter.reasoning?.enabled ? "adaptive" : "disabled" }, + effort: openrouter.verbosity, + } satisfies AnthropicProviderOptions + return result +} diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 4a0d9dd1500..12cbef52be7 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -6,6 +6,10 @@ import type { Provider } from "./provider" import type { ModelsDev } from "./models" import { iife } from "@/util/iife" import { Flag } from "@/flag/flag" +import type { AnthropicProviderOptions } from "@ai-sdk/anthropic" +import type { OpenAIResponsesProviderOptions } from "@ai-sdk/openai" +import type { OpenRouterProviderOptions } from "@openrouter/ai-sdk-provider" +import { kiloProviderOptions } from "@/kilocode/provider-options" type Modality = NonNullable["input"][number] @@ -936,6 +940,12 @@ export namespace ProviderTransform { return result } + // kilocode_change start + if (model.api.npm === "@kilocode/kilo-gateway") { + return kiloProviderOptions(options) + } + // kilocode_change end + const key = sdkKey(model.api.npm) ?? model.providerID return { [key]: options } } From bee882ad0b18d0584ffc35236181c2c792ad9e98 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Mar 2026 11:16:06 +0100 Subject: [PATCH 2/4] Remove unused imports --- packages/opencode/src/provider/transform.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 12cbef52be7..9e1ba8290a8 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -6,9 +6,6 @@ import type { Provider } from "./provider" import type { ModelsDev } from "./models" import { iife } from "@/util/iife" import { Flag } from "@/flag/flag" -import type { AnthropicProviderOptions } from "@ai-sdk/anthropic" -import type { OpenAIResponsesProviderOptions } from "@ai-sdk/openai" -import type { OpenRouterProviderOptions } from "@openrouter/ai-sdk-provider" import { kiloProviderOptions } from "@/kilocode/provider-options" type Modality = NonNullable["input"][number] From 6bb5776e5d5595fde914082bdc83386cbd4b019c Mon Sep 17 00:00:00 2001 From: Marius Date: Wed, 25 Mar 2026 11:17:52 +0100 Subject: [PATCH 3/4] fix(vscode, cli): handle Windows cross-drive paths in ignore checks (#7479) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows, path.relative() returns an absolute path when source and target are on different drives. The ignore npm package throws a RangeError when fed such paths. This caused 'Failed to send prompt' when VS Code had open tabs from another drive (e.g. extension settings in AppData while workspace is on D:). Guard all path.relative() → ignore.ignores() call sites against absolute results by checking path.isAbsolute() and a Windows drive-letter regex. --- packages/kilo-vscode/src/KiloProvider.ts | 4 +- .../shims/FileIgnoreController.ts | 7 ++- .../tests/unit/file-ignore-controller.test.ts | 57 +++++++++++++++++++ packages/opencode/src/file/index.ts | 5 +- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 04ddf9551c6..cb7e0a0b817 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -2471,7 +2471,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper const uri = tab.input.uri if (uri.scheme === "file") { const rel = path.relative(dir, uri.fsPath) - if (!rel.startsWith("..") && controller.validateAccess(uri.fsPath)) { + if (!rel.startsWith("..") && !path.isAbsolute(rel) && controller.validateAccess(uri.fsPath)) { result.add(rel.replaceAll("\\", "/")) } } @@ -2505,7 +2505,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper return undefined } const relative = path.relative(workspaceDir, fsPath) - if (relative.startsWith("..")) { + if (relative.startsWith("..") || path.isAbsolute(relative)) { return undefined } return relative diff --git a/packages/kilo-vscode/src/services/autocomplete/shims/FileIgnoreController.ts b/packages/kilo-vscode/src/services/autocomplete/shims/FileIgnoreController.ts index c314aacaff0..1b8c639cb48 100644 --- a/packages/kilo-vscode/src/services/autocomplete/shims/FileIgnoreController.ts +++ b/packages/kilo-vscode/src/services/autocomplete/shims/FileIgnoreController.ts @@ -10,6 +10,11 @@ const GITIGNORE = ".gitignore" */ const SENSITIVE_PATTERNS = [".env", ".env.*"] +// Matches Windows drive-letter absolute paths (e.g. "C:/" or "c:\"). +// path.isAbsolute() on POSIX does not recognise these, so we check explicitly +// to avoid passing them to the `ignore` package which throws a RangeError. +const WINDOWS_DRIVE = /^[a-zA-Z]:[/\\]/ + function toPosix(filePath: string): string { return filePath.replace(/\\/g, "/") } @@ -87,7 +92,7 @@ export class FileIgnoreController { } const relative = path.relative(this.workspacePath, resolved) - if (!relative || relative.startsWith("..")) { + if (!relative || relative.startsWith("..") || path.isAbsolute(relative) || WINDOWS_DRIVE.test(relative)) { return null } diff --git a/packages/kilo-vscode/tests/unit/file-ignore-controller.test.ts b/packages/kilo-vscode/tests/unit/file-ignore-controller.test.ts index 5b4e3131211..e590f92b364 100644 --- a/packages/kilo-vscode/tests/unit/file-ignore-controller.test.ts +++ b/packages/kilo-vscode/tests/unit/file-ignore-controller.test.ts @@ -2,8 +2,16 @@ import { afterEach, describe, expect, it } from "bun:test" import os from "node:os" import path from "node:path" import fs from "node:fs/promises" +import ignore from "ignore" import { FileIgnoreController } from "../../src/services/autocomplete/shims/FileIgnoreController" +// Activate Windows drive-letter detection in the `ignore` package. +// On actual Windows this runs automatically (process.platform === 'win32'); +// here we enable it explicitly so the test reproduces the Windows-only +// RangeError on any platform. +const setup = (ignore as any)[Symbol.for("setupWindows")] +if (typeof setup === "function") setup() + const tempDirs: string[] = [] afterEach(async () => { @@ -102,6 +110,55 @@ describe("FileIgnoreController", () => { }) }) + describe("Windows cross-drive paths", () => { + it("does not throw for a Windows-style absolute path from another drive", async () => { + const workspace = await createTempWorkspace() + await fs.writeFile(path.join(workspace, ".gitignore"), "node_modules/\n") + + const controller = new FileIgnoreController(workspace) + await controller.initialize() + + // Simulates a VS Code tab open on a file from a different Windows drive. + // On Windows, path.relative("D:\\project", "C:\\Users\\file") returns + // "C:\\Users\\file" (absolute), which the `ignore` package rejects via + // RangeError: path should be a `path.relative()`d string. + // + // On macOS, path.resolve joins "c:/..." relative to the workspace, + // producing "c:/Users/..." as the relative portion — still detected as + // a Windows drive letter by ignore's setupWindows() regex. + const cross = + "c:/Users/User/AppData/Roaming/Code/User/globalStorage/kilocode.kilo-code/settings/mcp_settings.json" + + expect(() => controller.validateAccess(cross)).not.toThrow() + expect(controller.validateAccess(cross)).toBe(false) + }) + + it("does not throw for file:// URIs with Windows drive letters", async () => { + const workspace = await createTempWorkspace() + await fs.writeFile(path.join(workspace, ".gitignore"), "node_modules/\n") + + const controller = new FileIgnoreController(workspace) + await controller.initialize() + + const uri = + "file:///c:/Users/User/AppData/Roaming/Code/User/globalStorage/kilocode.kilo-code/settings/mcp_settings.json" + + expect(() => controller.validateAccess(uri)).not.toThrow() + expect(controller.validateAccess(uri)).toBe(false) + }) + + it("still allows workspace files after cross-drive check", async () => { + const workspace = await createTempWorkspace() + await fs.writeFile(path.join(workspace, ".gitignore"), "node_modules/\n") + + const controller = new FileIgnoreController(workspace) + await controller.initialize() + + expect(controller.validateAccess(path.join(workspace, "src", "main.ts"))).toBe(true) + expect(controller.validateAccess(path.join(workspace, "node_modules", "foo.js"))).toBe(false) + }) + }) + describe("when constructed with empty workspace path", () => { it("denies all access", async () => { const controller = new FileIgnoreController("") diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 01f07c9afaa..2c181479be0 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -596,12 +596,15 @@ export namespace File { const fullPath = path.join(resolved, entry.name) const relativePath = path.relative(Instance.directory, fullPath) const type = entry.isDirectory() ? "directory" : "file" + // On Windows, path.relative() across drives returns an absolute path; + // skip the gitignore check in that case to avoid a RangeError from `ignore`. + const canIgnore = !path.isAbsolute(relativePath) nodes.push({ name: entry.name, path: relativePath, absolute: fullPath, type, - ignored: ignored(type === "directory" ? relativePath + "/" : relativePath), + ignored: canIgnore && ignored(type === "directory" ? relativePath + "/" : relativePath), }) } return nodes.sort((a, b) => { From 0f76380a2d6afe1eddf8ff9b38397991e00e2efc Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Mar 2026 11:19:03 +0100 Subject: [PATCH 4/4] Add openaiCompatible settings --- packages/opencode/src/kilocode/provider-options.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/opencode/src/kilocode/provider-options.ts b/packages/opencode/src/kilocode/provider-options.ts index 62fbe0de20e..e9b59e31ba3 100644 --- a/packages/opencode/src/kilocode/provider-options.ts +++ b/packages/opencode/src/kilocode/provider-options.ts @@ -1,5 +1,6 @@ import type { AnthropicProviderOptions } from "@ai-sdk/anthropic" import type { OpenAIResponsesProviderOptions } from "@ai-sdk/openai" +import type { OpenAICompatibleProviderOptions } from "@ai-sdk/openai-compatible" import type { OpenRouterProviderOptions } from "@openrouter/ai-sdk-provider" export function kiloProviderOptions(options: { [x: string]: any }) { @@ -19,5 +20,10 @@ export function kiloProviderOptions(options: { [x: string]: any }) { thinking: { type: openrouter.reasoning?.enabled ? "adaptive" : "disabled" }, effort: openrouter.verbosity, } satisfies AnthropicProviderOptions + result.openaiCompatible = { + reasoningEffort: + openrouter.reasoning && "effort" in openrouter.reasoning ? openrouter.reasoning?.effort : undefined, + textVerbosity: openrouter.verbosity, + } satisfies OpenAICompatibleProviderOptions return result }