diff --git a/src/package.json b/src/package.json index 83ea46580a..57a29adb85 100644 --- a/src/package.json +++ b/src/package.json @@ -453,6 +453,18 @@ "type": "string", "default": "", "description": "%settings.autoImportSettingsPath.description%" + }, + "kilo-code.baseUrl": { + "type": "string", + "default": "https://kilocode.ai", + "description": "TEST Base URL for Kilo Code backend services (Advanced)", + "scope": "application", + "tags": [ + "advanced", + "usesOnlineServices" + ], + "order": 999, + "editPresentation": "multilineText" } } } diff --git a/webview-ui/src/components/kilocode/__tests__/helpers.spec.ts b/webview-ui/src/components/kilocode/__tests__/helpers.spec.ts new file mode 100644 index 0000000000..8290beb419 --- /dev/null +++ b/webview-ui/src/components/kilocode/__tests__/helpers.spec.ts @@ -0,0 +1,168 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" + +const mockPostMessage = vi.fn() +vi.mock("@/utils/vscode", () => ({ + vscode: { + postMessage: mockPostMessage, + }, +})) + +describe("helpers", () => { + let originalAddEventListener: typeof window.addEventListener + let mockEventListener: ReturnType + + beforeEach(() => { + mockPostMessage.mockClear() + originalAddEventListener = window.addEventListener + mockEventListener = vi.fn() + window.addEventListener = mockEventListener + vi.resetModules() + }) + + afterEach(() => { + window.addEventListener = originalAddEventListener + }) + + describe("getKiloCodeBackendSignInUrl", () => { + it("should return sign-in URL with default base URL", async () => { + const { getKiloCodeBackendSignInUrl } = await import("../helpers") + + const result = getKiloCodeBackendSignInUrl() + + expect(result).toBe("https://kilocode.ai/users/sign_in?source=vscode") + expect(mockPostMessage).toHaveBeenCalledWith({ + type: "getVSCodeSetting", + setting: "kilo-code.baseUrl", + }) + }) + + it("should use cached base URL after setting is received", async () => { + const { getKiloCodeBackendSignInUrl } = await import("../helpers") + + // Simulate receiving a setting update + const messageEvent = new MessageEvent("message", { + data: { + type: "vsCodeSetting", + setting: "kilo-code.baseUrl", + value: "https://custom.example.com", + }, + }) + + // Get the event listener that was registered + expect(mockEventListener).toHaveBeenCalledWith("message", expect.any(Function)) + const eventHandler = mockEventListener.mock.calls[0][1] + + // Trigger the event handler + eventHandler(messageEvent) + + const result = getKiloCodeBackendSignInUrl() + expect(result).toBe("https://custom.example.com/users/sign_in?source=vscode") + }) + + it("should fallback to default when setting value is empty", async () => { + const { getKiloCodeBackendSignInUrl } = await import("../helpers") + + // Simulate receiving an empty setting value + const messageEvent = new MessageEvent("message", { + data: { + type: "vsCodeSetting", + setting: "kilo-code.baseUrl", + value: "", + }, + }) + + const eventHandler = mockEventListener.mock.calls[0][1] + eventHandler(messageEvent) + + const result = getKiloCodeBackendSignInUrl() + expect(result).toBe("https://kilocode.ai/users/sign_in?source=vscode") + }) + + it("should support custom uriScheme and uiKind parameters", async () => { + const { getKiloCodeBackendSignInUrl } = await import("../helpers") + + const result = getKiloCodeBackendSignInUrl("custom-scheme", "Web") + expect(result).toBe("https://kilocode.ai/users/sign_in?source=web") + }) + }) + + describe("getKiloCodeBackendSignUpUrl", () => { + it("should return sign-up URL with default base URL", async () => { + const { getKiloCodeBackendSignUpUrl } = await import("../helpers") + + const result = getKiloCodeBackendSignUpUrl() + + expect(result).toBe("https://kilocode.ai/users/sign_up?source=vscode") + expect(mockPostMessage).toHaveBeenCalledWith({ + type: "getVSCodeSetting", + setting: "kilo-code.baseUrl", + }) + }) + + it("should use cached base URL after setting is received", async () => { + const { getKiloCodeBackendSignUpUrl } = await import("../helpers") + + // Simulate receiving a setting update + const messageEvent = new MessageEvent("message", { + data: { + type: "vsCodeSetting", + setting: "kilo-code.baseUrl", + value: "https://custom.example.com", + }, + }) + + const eventHandler = mockEventListener.mock.calls[0][1] + eventHandler(messageEvent) + + const result = getKiloCodeBackendSignUpUrl() + expect(result).toBe("https://custom.example.com/users/sign_up?source=vscode") + }) + + it("should support custom uriScheme and uiKind parameters", async () => { + const { getKiloCodeBackendSignUpUrl } = await import("../helpers") + + const result = getKiloCodeBackendSignUpUrl("custom-scheme", "Web") + expect(result).toBe("https://kilocode.ai/users/sign_up?source=web") + }) + }) + + describe("message event handling", () => { + it("should ignore non-vsCodeSetting messages", async () => { + await import("../helpers") + + const messageEvent = new MessageEvent("message", { + data: { + type: "otherMessage", + setting: "kilo-code.baseUrl", + value: "https://should-be-ignored.com", + }, + }) + + const eventHandler = mockEventListener.mock.calls[0][1] + eventHandler(messageEvent) + + const { getKiloCodeBackendSignInUrl } = await import("../helpers") + const result = getKiloCodeBackendSignInUrl() + expect(result).toBe("https://kilocode.ai/users/sign_in?source=vscode") + }) + + it("should ignore messages for different settings", async () => { + await import("../helpers") + + const messageEvent = new MessageEvent("message", { + data: { + type: "vsCodeSetting", + setting: "other.setting", + value: "https://should-be-ignored.com", + }, + }) + + const eventHandler = mockEventListener.mock.calls[0][1] + eventHandler(messageEvent) + + const { getKiloCodeBackendSignInUrl } = await import("../helpers") + const result = getKiloCodeBackendSignInUrl() + expect(result).toBe("https://kilocode.ai/users/sign_in?source=vscode") + }) + }) +}) diff --git a/webview-ui/src/components/kilocode/helpers.ts b/webview-ui/src/components/kilocode/helpers.ts index 0d613dbd13..9f76d6aa21 100644 --- a/webview-ui/src/components/kilocode/helpers.ts +++ b/webview-ui/src/components/kilocode/helpers.ts @@ -1,11 +1,37 @@ +import { vscode } from "@/utils/vscode" + +// Global state for base URL +let cachedBaseUrl: string = "https://kilocode.ai" +let hasRequestedSetting = false + +// Request the setting from VS Code on first use +function ensureBaseUrlRequested() { + if (!hasRequestedSetting) { + hasRequestedSetting = true + vscode.postMessage({ type: "getVSCodeSetting", setting: "kilo-code.baseUrl" }) + } +} + +// Listen for VS Code setting responses +window.addEventListener("message", (event) => { + if (event.data?.type === "vsCodeSetting" && event.data?.setting === "kilo-code.baseUrl") { + cachedBaseUrl = event.data.value || "https://kilocode.ai" + } +}) + +function getBaseUrl(): string { + ensureBaseUrlRequested() + return cachedBaseUrl +} + export function getKiloCodeBackendSignInUrl(uriScheme: string = "vscode", uiKind: string = "Desktop") { - const baseUrl = "https://kilocode.ai" + const baseUrl = getBaseUrl() const source = uiKind === "Web" ? "web" : uriScheme return `${baseUrl}/users/sign_in?source=${source}` } export function getKiloCodeBackendSignUpUrl(uriScheme: string = "vscode", uiKind: string = "Desktop") { - const baseUrl = "https://kilocode.ai" + const baseUrl = getBaseUrl() const source = uiKind === "Web" ? "web" : uriScheme return `${baseUrl}/users/sign_up?source=${source}` }