From b8daa7557e0081769907de9ff280bd1a2e89d3a0 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 10 Oct 2025 13:29:16 +0200 Subject: [PATCH 01/46] also show in the settings when no provider is available --- src/services/ghost/GhostModel.ts | 8 ++-- .../src/components/settings/ApiOptions.tsx | 13 ++++++ .../settings/__tests__/ApiOptions.spec.tsx | 42 +++++++++++++++++++ webview-ui/src/i18n/locales/en/settings.json | 1 + 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index fae2d99e01f..7a1cbbedf56 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -8,7 +8,7 @@ import { ApiStreamChunk } from "../../api/transform/stream" const KILOCODE_DEFAULT_MODEL = "mistralai/codestral-2508" const MISTRAL_DEFAULT_MODEL = "codestral-latest" -const SUPPORTED_DEFAULT_PROVIDERS = ["mistral", "kilocode", "openrouter"] +export const SUPPORTED_AUTOCOMPLETE_PROVIDERS = ["mistral", "kilocode", "openrouter"] export class GhostModel { private apiHandler: ApiHandler | null = null @@ -24,7 +24,7 @@ export class GhostModel { public async reload(settings: GhostServiceSettings, providerSettingsManager: ProviderSettingsManager) { const profiles = await providerSettingsManager.listConfig() const validProfiles = profiles - .filter((x) => x.apiProvider && SUPPORTED_DEFAULT_PROVIDERS.includes(x.apiProvider)) + .filter((x) => x.apiProvider && SUPPORTED_AUTOCOMPLETE_PROVIDERS.includes(x.apiProvider)) .sort((a, b) => { if (!a.apiProvider) { return 1 // Place undefined providers at the end @@ -33,8 +33,8 @@ export class GhostModel { return -1 // Place undefined providers at the beginning } return ( - SUPPORTED_DEFAULT_PROVIDERS.indexOf(a.apiProvider) - - SUPPORTED_DEFAULT_PROVIDERS.indexOf(b.apiProvider) + SUPPORTED_AUTOCOMPLETE_PROVIDERS.indexOf(a.apiProvider) - + SUPPORTED_AUTOCOMPLETE_PROVIDERS.indexOf(b.apiProvider) ) }) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 91565def48b..4cf0caac918 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -49,6 +49,8 @@ import { useAppTranslation } from "@src/i18n/TranslationContext" import { useRouterModels } from "@src/components/ui/hooks/useRouterModels" import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel" import { useExtensionState } from "@src/context/ExtensionStateContext" + +const SUPPORTED_AUTOCOMPLETE_PROVIDERS = ["mistral", "kilocode", "openrouter"] // kilocode_change start //import { // useOpenRouterModelProviders, @@ -495,6 +497,17 @@ const ApiOptions = ({ /> + {!SUPPORTED_AUTOCOMPLETE_PROVIDERS.includes(selectedProvider) && ( +
+ +
+ {t("settings:providers.autocompleteNotSupported", { + providers: SUPPORTED_AUTOCOMPLETE_PROVIDERS.join(", "), + })} +
+
+ )} + {errorMessage && } {/* kilocode_change start */} diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx index c82518864a4..096fdf71b58 100644 --- a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx @@ -573,4 +573,46 @@ describe("ApiOptions", () => { expect(screen.queryByTestId("litellm-provider")).not.toBeInTheDocument() }) }) + + describe("Autocomplete provider warning", () => { + it("shows warning for unsupported providers", () => { + renderApiOptions({ + apiConfiguration: { + apiProvider: "anthropic", + }, + }) + + expect(screen.getByText("settings:providers.autocompleteNotSupported")).toBeInTheDocument() + }) + + it("does not show warning for supported providers - mistral", () => { + renderApiOptions({ + apiConfiguration: { + apiProvider: "mistral", + }, + }) + + expect(screen.queryByText("settings:providers.autocompleteNotSupported")).not.toBeInTheDocument() + }) + + it("does not show warning for supported providers - kilocode", () => { + renderApiOptions({ + apiConfiguration: { + apiProvider: "kilocode", + }, + }) + + expect(screen.queryByText("settings:providers.autocompleteNotSupported")).not.toBeInTheDocument() + }) + + it("does not show warning for supported providers - openrouter", () => { + renderApiOptions({ + apiConfiguration: { + apiProvider: "openrouter", + }, + }) + + expect(screen.queryByText("settings:providers.autocompleteNotSupported")).not.toBeInTheDocument() + }) + }) }) diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 31dbb4b2a8f..b6e71d5ac2c 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -235,6 +235,7 @@ "description": "Save different API configurations to quickly switch between providers and settings.", "apiProvider": "API Provider", "model": "Model", + "autocompleteNotSupported": "⚠️ Autocomplete is only supported with the following providers: {{providers}}", "nameEmpty": "Name cannot be empty", "nameExists": "A profile with this name already exists", "deleteProfile": "Delete Profile", From e0000d3cb993a917d003cb0ff7292b62129860bc Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 10 Oct 2025 14:14:33 +0200 Subject: [PATCH 02/46] deduplicate --- packages/types/src/provider-settings.ts | 4 ++++ src/services/ghost/GhostModel.ts | 16 ++++++++-------- .../src/components/settings/ApiOptions.tsx | 5 ++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 0dfb7f19c75..9e61d225625 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -37,6 +37,10 @@ import { toolUseStylesSchema } from "./kilocode/native-function-calling.js" export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3 +export const SUPPORTED_AUTOCOMPLETE_PROVIDERS = ["mistral", "kilocode", "openrouter"] as const + +export type SupportedAutocompleteProvider = (typeof SUPPORTED_AUTOCOMPLETE_PROVIDERS)[number] + /** * DynamicProvider * diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 7a1cbbedf56..03971439cdf 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -1,4 +1,4 @@ -import { GhostServiceSettings } from "@roo-code/types" +import { GhostServiceSettings, SUPPORTED_AUTOCOMPLETE_PROVIDERS } from "@roo-code/types" import { ApiHandler, buildApiHandler } from "../../api" import { ContextProxy } from "../../core/config/ContextProxy" import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager" @@ -8,8 +8,6 @@ import { ApiStreamChunk } from "../../api/transform/stream" const KILOCODE_DEFAULT_MODEL = "mistralai/codestral-2508" const MISTRAL_DEFAULT_MODEL = "codestral-latest" -export const SUPPORTED_AUTOCOMPLETE_PROVIDERS = ["mistral", "kilocode", "openrouter"] - export class GhostModel { private apiHandler: ApiHandler | null = null public loaded = false @@ -24,17 +22,19 @@ export class GhostModel { public async reload(settings: GhostServiceSettings, providerSettingsManager: ProviderSettingsManager) { const profiles = await providerSettingsManager.listConfig() const validProfiles = profiles - .filter((x) => x.apiProvider && SUPPORTED_AUTOCOMPLETE_PROVIDERS.includes(x.apiProvider)) + .filter( + (x) => x.apiProvider && (SUPPORTED_AUTOCOMPLETE_PROVIDERS as readonly string[]).includes(x.apiProvider), + ) .sort((a, b) => { if (!a.apiProvider) { - return 1 // Place undefined providers at the end + return 1 } if (!b.apiProvider) { - return -1 // Place undefined providers at the beginning + return -1 } return ( - SUPPORTED_AUTOCOMPLETE_PROVIDERS.indexOf(a.apiProvider) - - SUPPORTED_AUTOCOMPLETE_PROVIDERS.indexOf(b.apiProvider) + (SUPPORTED_AUTOCOMPLETE_PROVIDERS as readonly string[]).indexOf(a.apiProvider) - + (SUPPORTED_AUTOCOMPLETE_PROVIDERS as readonly string[]).indexOf(b.apiProvider) ) }) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 4cf0caac918..78b3c93d6f2 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -8,6 +8,7 @@ import { type ProviderName, type ProviderSettings, DEFAULT_CONSECUTIVE_MISTAKE_LIMIT, + SUPPORTED_AUTOCOMPLETE_PROVIDERS, openRouterDefaultModelId, requestyDefaultModelId, glamaDefaultModelId, @@ -49,8 +50,6 @@ import { useAppTranslation } from "@src/i18n/TranslationContext" import { useRouterModels } from "@src/components/ui/hooks/useRouterModels" import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel" import { useExtensionState } from "@src/context/ExtensionStateContext" - -const SUPPORTED_AUTOCOMPLETE_PROVIDERS = ["mistral", "kilocode", "openrouter"] // kilocode_change start //import { // useOpenRouterModelProviders, @@ -497,7 +496,7 @@ const ApiOptions = ({ /> - {!SUPPORTED_AUTOCOMPLETE_PROVIDERS.includes(selectedProvider) && ( + {!(SUPPORTED_AUTOCOMPLETE_PROVIDERS as readonly string[]).includes(selectedProvider) && (
From b2e4811b858162eff9b6bab2d490b1e7bba62d7d Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 10 Oct 2025 14:17:35 +0200 Subject: [PATCH 03/46] remove unused import --- src/services/ghost/GhostModel.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 03971439cdf..47d958b698c 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -1,6 +1,5 @@ import { GhostServiceSettings, SUPPORTED_AUTOCOMPLETE_PROVIDERS } from "@roo-code/types" import { ApiHandler, buildApiHandler } from "../../api" -import { ContextProxy } from "../../core/config/ContextProxy" import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager" import { OpenRouterHandler } from "../../api/providers" import { ApiStreamChunk } from "../../api/transform/stream" From 1c6a4168d6e7ec7d756af58e2a4f27de19053fc3 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 10 Oct 2025 14:19:56 +0200 Subject: [PATCH 04/46] remove unused param --- src/services/ghost/GhostModel.ts | 4 ++-- src/services/ghost/GhostProvider.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 47d958b698c..7e9f398ea14 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -1,4 +1,4 @@ -import { GhostServiceSettings, SUPPORTED_AUTOCOMPLETE_PROVIDERS } from "@roo-code/types" +import { SUPPORTED_AUTOCOMPLETE_PROVIDERS } from "@roo-code/types" import { ApiHandler, buildApiHandler } from "../../api" import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager" import { OpenRouterHandler } from "../../api/providers" @@ -18,7 +18,7 @@ export class GhostModel { } } - public async reload(settings: GhostServiceSettings, providerSettingsManager: ProviderSettingsManager) { + public async reload(providerSettingsManager: ProviderSettingsManager) { const profiles = await providerSettingsManager.listConfig() const validProfiles = profiles .filter( diff --git a/src/services/ghost/GhostProvider.ts b/src/services/ghost/GhostProvider.ts index d4bea3cd9c9..bd06e533542 100644 --- a/src/services/ghost/GhostProvider.ts +++ b/src/services/ghost/GhostProvider.ts @@ -124,7 +124,7 @@ export class GhostProvider { public async load() { this.settings = this.loadSettings() - await this.model.reload(this.settings, this.providerSettingsManager) + await this.model.reload(this.providerSettingsManager) this.cursorAnimation.updateSettings(this.settings || undefined) await this.updateGlobalContext() this.updateStatusBar() From 651d87a68a6964adc3311075555e068a2eb70341 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 10 Oct 2025 15:16:48 +0200 Subject: [PATCH 05/46] move model options together --- packages/types/src/provider-settings.ts | 10 +++++++++- src/services/ghost/GhostModel.ts | 11 ++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 9e61d225625..0eb9550d3d6 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -37,7 +37,15 @@ import { toolUseStylesSchema } from "./kilocode/native-function-calling.js" export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3 -export const SUPPORTED_AUTOCOMPLETE_PROVIDERS = ["mistral", "kilocode", "openrouter"] as const +export const AUTOCOMPLETE_PROVIDER_MODELS = { + mistral: "codestral-latest", + kilocode: "mistralai/codestral-2508", + openrouter: "mistralai/codestral-2508", +} as const + +export const SUPPORTED_AUTOCOMPLETE_PROVIDERS = Object.keys( + AUTOCOMPLETE_PROVIDER_MODELS, +) as (keyof typeof AUTOCOMPLETE_PROVIDER_MODELS)[] export type SupportedAutocompleteProvider = (typeof SUPPORTED_AUTOCOMPLETE_PROVIDERS)[number] diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 7e9f398ea14..70477accc2b 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -1,12 +1,9 @@ -import { SUPPORTED_AUTOCOMPLETE_PROVIDERS } from "@roo-code/types" +import { SUPPORTED_AUTOCOMPLETE_PROVIDERS, AUTOCOMPLETE_PROVIDER_MODELS } from "@roo-code/types" import { ApiHandler, buildApiHandler } from "../../api" import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager" import { OpenRouterHandler } from "../../api/providers" import { ApiStreamChunk } from "../../api/transform/stream" -const KILOCODE_DEFAULT_MODEL = "mistralai/codestral-2508" -const MISTRAL_DEFAULT_MODEL = "codestral-latest" - export class GhostModel { private apiHandler: ApiHandler | null = null public loaded = false @@ -46,15 +43,15 @@ export class GhostModel { let modelDefinition = {} if (profileProvider === "kilocode") { modelDefinition = { - kilocodeModel: KILOCODE_DEFAULT_MODEL, + kilocodeModel: AUTOCOMPLETE_PROVIDER_MODELS.kilocode, } } else if (profileProvider === "openrouter") { modelDefinition = { - openRouterModelId: KILOCODE_DEFAULT_MODEL, + openRouterModelId: AUTOCOMPLETE_PROVIDER_MODELS.openrouter, } } else if (profileProvider === "mistral") { modelDefinition = { - apiModelId: MISTRAL_DEFAULT_MODEL, + apiModelId: AUTOCOMPLETE_PROVIDER_MODELS.mistral, } } this.apiHandler = buildApiHandler({ From 68526fefae8f7b6ed1b5a406c8854e3ed2a9be9a Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 10 Oct 2025 15:47:41 +0200 Subject: [PATCH 06/46] typesafe --- packages/types/src/provider-settings.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 0eb9550d3d6..8a524c707f6 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -37,18 +37,6 @@ import { toolUseStylesSchema } from "./kilocode/native-function-calling.js" export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3 -export const AUTOCOMPLETE_PROVIDER_MODELS = { - mistral: "codestral-latest", - kilocode: "mistralai/codestral-2508", - openrouter: "mistralai/codestral-2508", -} as const - -export const SUPPORTED_AUTOCOMPLETE_PROVIDERS = Object.keys( - AUTOCOMPLETE_PROVIDER_MODELS, -) as (keyof typeof AUTOCOMPLETE_PROVIDER_MODELS)[] - -export type SupportedAutocompleteProvider = (typeof SUPPORTED_AUTOCOMPLETE_PROVIDERS)[number] - /** * DynamicProvider * @@ -175,6 +163,18 @@ export type ProviderName = z.infer export const isProviderName = (key: unknown): key is ProviderName => typeof key === "string" && providerNames.includes(key as ProviderName) +export const AUTOCOMPLETE_PROVIDER_MODELS = { + mistral: "codestral-latest", + kilocode: "mistralai/codestral-2508", + openrouter: "mistralai/codestral-2508", +} as const satisfies Record, string> + +export const SUPPORTED_AUTOCOMPLETE_PROVIDERS = Object.keys( + AUTOCOMPLETE_PROVIDER_MODELS, +) as (keyof typeof AUTOCOMPLETE_PROVIDER_MODELS)[] + +export type SupportedAutocompleteProvider = (typeof SUPPORTED_AUTOCOMPLETE_PROVIDERS)[number] + /** * ProviderSettingsEntry */ From 6a76b4015a8773a68bd6646e02c8fd9ef485a94e Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 10 Oct 2025 15:54:17 +0200 Subject: [PATCH 07/46] simplify --- packages/types/src/provider-settings.ts | 6 ------ src/services/ghost/GhostModel.ts | 12 ++++-------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 8a524c707f6..0b80ad3246c 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -169,12 +169,6 @@ export const AUTOCOMPLETE_PROVIDER_MODELS = { openrouter: "mistralai/codestral-2508", } as const satisfies Record, string> -export const SUPPORTED_AUTOCOMPLETE_PROVIDERS = Object.keys( - AUTOCOMPLETE_PROVIDER_MODELS, -) as (keyof typeof AUTOCOMPLETE_PROVIDER_MODELS)[] - -export type SupportedAutocompleteProvider = (typeof SUPPORTED_AUTOCOMPLETE_PROVIDERS)[number] - /** * ProviderSettingsEntry */ diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 70477accc2b..5fd9fbfdcb5 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -1,4 +1,4 @@ -import { SUPPORTED_AUTOCOMPLETE_PROVIDERS, AUTOCOMPLETE_PROVIDER_MODELS } from "@roo-code/types" +import { AUTOCOMPLETE_PROVIDER_MODELS } from "@roo-code/types" import { ApiHandler, buildApiHandler } from "../../api" import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager" import { OpenRouterHandler } from "../../api/providers" @@ -17,10 +17,9 @@ export class GhostModel { public async reload(providerSettingsManager: ProviderSettingsManager) { const profiles = await providerSettingsManager.listConfig() + const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) const validProfiles = profiles - .filter( - (x) => x.apiProvider && (SUPPORTED_AUTOCOMPLETE_PROVIDERS as readonly string[]).includes(x.apiProvider), - ) + .filter((x) => x.apiProvider && x.apiProvider in AUTOCOMPLETE_PROVIDER_MODELS) .sort((a, b) => { if (!a.apiProvider) { return 1 @@ -28,10 +27,7 @@ export class GhostModel { if (!b.apiProvider) { return -1 } - return ( - (SUPPORTED_AUTOCOMPLETE_PROVIDERS as readonly string[]).indexOf(a.apiProvider) - - (SUPPORTED_AUTOCOMPLETE_PROVIDERS as readonly string[]).indexOf(b.apiProvider) - ) + return supportedProviders.indexOf(a.apiProvider) - supportedProviders.indexOf(b.apiProvider) }) const selectedProfile = validProfiles[0] || null From f1cfd029754337141eb4956c2ef7f44fd2814c2d Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Tue, 14 Oct 2025 16:57:26 +0200 Subject: [PATCH 08/46] fix --- .../components/settings/__tests__/ApiOptions.spec.tsx | 9 +++++++++ .../components/settings/__tests__/SettingsView.spec.tsx | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx index 096fdf71b58..27450ab65b5 100644 --- a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx @@ -9,6 +9,15 @@ import { ExtensionStateContextProvider } from "@src/context/ExtensionStateContex import ApiOptions, { ApiOptionsProps } from "../ApiOptions" +// Mock @roo-code/types constants +vi.mock("@roo-code/types", async () => { + const actual = await vi.importActual("@roo-code/types") + return { + ...actual, + SUPPORTED_AUTOCOMPLETE_PROVIDERS: ["mistral", "kilocode", "openrouter"], + } +}) + // Mock VSCode components vi.mock("@vscode/webview-ui-toolkit/react", () => ({ VSCodeTextField: ({ children, value, onBlur }: any) => ( diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx index 5a9ac4d1e0c..a3440de8733 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx @@ -8,6 +8,15 @@ import SettingsView from "../SettingsView" vi.mock("@src/utils/vscode", () => ({ vscode: { postMessage: vi.fn() } })) +// Mock @roo-code/types constants +vi.mock("@roo-code/types", async () => { + const actual = await vi.importActual("@roo-code/types") + return { + ...actual, + SUPPORTED_AUTOCOMPLETE_PROVIDERS: ["mistral", "kilocode", "openrouter"], + } +}) + // kilocode_change start // Mock the validate functions to prevent validation errors vi.mock("@src/utils/validate", () => ({ From 20138b5347ab6ccdd5ef2366703b278c9117e9e5 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 08:57:29 +0200 Subject: [PATCH 09/46] add missing translations --- webview-ui/src/i18n/locales/ar/settings.json | 1 + webview-ui/src/i18n/locales/ca/settings.json | 1 + webview-ui/src/i18n/locales/cs/settings.json | 1 + webview-ui/src/i18n/locales/de/settings.json | 1 + webview-ui/src/i18n/locales/es/settings.json | 1 + webview-ui/src/i18n/locales/fr/settings.json | 1 + webview-ui/src/i18n/locales/hi/settings.json | 1 + webview-ui/src/i18n/locales/id/settings.json | 1 + webview-ui/src/i18n/locales/it/settings.json | 1 + webview-ui/src/i18n/locales/ja/settings.json | 1 + webview-ui/src/i18n/locales/ko/settings.json | 1 + webview-ui/src/i18n/locales/nl/settings.json | 1 + webview-ui/src/i18n/locales/pl/settings.json | 1 + webview-ui/src/i18n/locales/pt-BR/settings.json | 1 + webview-ui/src/i18n/locales/ru/settings.json | 1 + webview-ui/src/i18n/locales/th/settings.json | 1 + webview-ui/src/i18n/locales/tr/settings.json | 1 + webview-ui/src/i18n/locales/uk/settings.json | 1 + webview-ui/src/i18n/locales/vi/settings.json | 1 + webview-ui/src/i18n/locales/zh-CN/settings.json | 1 + webview-ui/src/i18n/locales/zh-TW/settings.json | 1 + 21 files changed, 21 insertions(+) diff --git a/webview-ui/src/i18n/locales/ar/settings.json b/webview-ui/src/i18n/locales/ar/settings.json index 960b92fac54..480f1c75ce8 100644 --- a/webview-ui/src/i18n/locales/ar/settings.json +++ b/webview-ui/src/i18n/locales/ar/settings.json @@ -239,6 +239,7 @@ "description": "احفظ إعدادات متعددة للتبديل السريع بين مزوّدين ونماذج.", "apiProvider": "مزود API", "model": "النموذج", + "autocompleteNotSupported": "⚠️ الإكمال التلقائي مدعوم فقط مع المزودين التاليين: {{providers}}", "nameEmpty": "الاسم ما يصير فاضي", "nameExists": "الاسم مستخدم مسبقًا", "deleteProfile": "حذف الملف", diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 179c62668b8..6880e2f7ef4 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -230,6 +230,7 @@ "description": "Deseu diferents configuracions d'API per canviar ràpidament entre proveïdors i configuracions.", "apiProvider": "Proveïdor d'API", "model": "Model", + "autocompleteNotSupported": "⚠️ L'autocompletat només és compatible amb els següents proveïdors: {{providers}}", "nameEmpty": "El nom no pot estar buit", "nameExists": "Ja existeix un perfil amb aquest nom", "deleteProfile": "Esborrar perfil", diff --git a/webview-ui/src/i18n/locales/cs/settings.json b/webview-ui/src/i18n/locales/cs/settings.json index 03621a6ac66..3cd6d534c36 100644 --- a/webview-ui/src/i18n/locales/cs/settings.json +++ b/webview-ui/src/i18n/locales/cs/settings.json @@ -239,6 +239,7 @@ "description": "Uložte si různé konfigurace API pro rychlé přepínání mezi poskytovateli a nastaveními.", "apiProvider": "Poskytovatel API", "model": "Model", + "autocompleteNotSupported": "⚠️ Automatické dokončování je podporováno pouze s následujícími poskytovateli: {{providers}}", "nameEmpty": "Jméno nemůže být prázdné", "nameExists": "Profil s tímto názvem již existuje", "deleteProfile": "Smazat profil", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 37acc150c78..fc172ae9c3d 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -230,6 +230,7 @@ "description": "Speicher verschiedene API-Konfigurationen, um schnell zwischen Anbietern und Einstellungen zu wechseln.", "apiProvider": "API-Anbieter", "model": "Modell", + "autocompleteNotSupported": "⚠️ Autovervollständigung wird nur mit folgenden Anbietern unterstützt: {{providers}}", "nameEmpty": "Name darf nicht leer sein", "nameExists": "Ein Profil mit diesem Namen existiert bereits", "deleteProfile": "Profil löschen", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index bce2c528a15..b616aa44eaf 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -230,6 +230,7 @@ "description": "Guarde diferentes configuraciones de API para cambiar rápidamente entre proveedores y ajustes.", "apiProvider": "Proveedor de API", "model": "Modelo", + "autocompleteNotSupported": "⚠️ El autocompletado solo es compatible con los siguientes proveedores: {{providers}}", "nameEmpty": "El nombre no puede estar vacío", "nameExists": "Ya existe un perfil con este nombre", "deleteProfile": "Eliminar perfil", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 8e754e7381e..13935e0973a 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -230,6 +230,7 @@ "description": "Enregistrez différentes configurations d'API pour basculer rapidement entre les fournisseurs et les paramètres.", "apiProvider": "Fournisseur d'API", "model": "Modèle", + "autocompleteNotSupported": "⚠️ L'autocomplétion n'est prise en charge qu'avec les fournisseurs suivants : {{providers}}", "nameEmpty": "Le nom ne peut pas être vide", "nameExists": "Un profil avec ce nom existe déjà", "deleteProfile": "Supprimer le profil", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 2c7f7fcbdf1..0e8b117c1f5 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -230,6 +230,7 @@ "description": "विभिन्न API कॉन्फ़िगरेशन सहेजें ताकि प्रदाताओं और सेटिंग्स के बीच त्वरित रूप से स्विच कर सकें।", "apiProvider": "API प्रदाता", "model": "मॉडल", + "autocompleteNotSupported": "⚠️ स्वतः पूर्ण केवल निम्नलिखित प्रदाताओं के साथ समर्थित है: {{providers}}", "nameEmpty": "नाम खाली नहीं हो सकता", "nameExists": "इस नाम वाला प्रोफ़ाइल पहले से मौजूद है", "deleteProfile": "प्रोफ़ाइल हटाएं", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index d72cfd58e58..6a9e9392f8c 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -230,6 +230,7 @@ "description": "Simpan konfigurasi API yang berbeda untuk beralih dengan cepat antara provider dan pengaturan.", "apiProvider": "Provider API", "model": "Model", + "autocompleteNotSupported": "⚠️ Pelengkapan otomatis hanya didukung dengan penyedia berikut: {{providers}}", "nameEmpty": "Nama tidak boleh kosong", "nameExists": "Profil dengan nama ini sudah ada", "deleteProfile": "Hapus Profil", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 737f200f6a5..3c4e07be43a 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -231,6 +231,7 @@ "description": "Salva diverse configurazioni API per passare rapidamente tra fornitori e impostazioni.", "apiProvider": "Fornitore API", "model": "Modello", + "autocompleteNotSupported": "⚠️ Il completamento automatico è supportato solo con i seguenti provider: {{providers}}", "nameEmpty": "Il nome non può essere vuoto", "nameExists": "Esiste già un profilo con questo nome", "deleteProfile": "Elimina profilo", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 882183a413d..cac97304d9f 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -231,6 +231,7 @@ "description": "異なるAPI設定を保存して、プロバイダーと設定をすばやく切り替えることができます。", "apiProvider": "APIプロバイダー", "model": "モデル", + "autocompleteNotSupported": "⚠️ オートコンプリートは次のプロバイダーでのみサポートされています: {{providers}}", "nameEmpty": "名前を空にすることはできません", "nameExists": "この名前のプロファイルは既に存在します", "deleteProfile": "プロファイルを削除", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index b02bdad689b..a0a0894b569 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -230,6 +230,7 @@ "description": "다양한 API 구성을 저장하여 제공자와 설정 간에 빠르게 전환할 수 있습니다.", "apiProvider": "API 제공자", "model": "모델", + "autocompleteNotSupported": "⚠️ 자동 완성은 다음 제공자에서만 지원됩니다: {{providers}}", "nameEmpty": "이름은 비워둘 수 없습니다", "nameExists": "이 이름의 프로필이 이미 존재합니다", "deleteProfile": "프로필 삭제", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index a40ee3100e6..20629016dbc 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -230,6 +230,7 @@ "description": "Sla verschillende API-configuraties op om snel te wisselen tussen providers en instellingen.", "apiProvider": "API-provider", "model": "Model", + "autocompleteNotSupported": "⚠️ Automatisch aanvullen wordt alleen ondersteund met de volgende providers: {{providers}}", "nameEmpty": "Naam mag niet leeg zijn", "nameExists": "Er bestaat al een profiel met deze naam", "deleteProfile": "Profiel verwijderen", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index c0a19078212..cbf50aab1e6 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -230,6 +230,7 @@ "description": "Zapisz różne konfiguracje API, aby szybko przełączać się między dostawcami i ustawieniami.", "apiProvider": "Dostawca API", "model": "Model", + "autocompleteNotSupported": "⚠️ Autouzupełnianie jest obsługiwane tylko z następującymi dostawcami: {{providers}}", "nameEmpty": "Nazwa nie może być pusta", "nameExists": "Profil o tej nazwie już istnieje", "deleteProfile": "Usuń profil", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 7f4624e8630..79ae74d0c60 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -230,6 +230,7 @@ "description": "Salve diferentes configurações de API para alternar rapidamente entre provedores e configurações.", "apiProvider": "Provedor de API", "model": "Modelo", + "autocompleteNotSupported": "⚠️ O preenchimento automático só é compatível com os seguintes provedores: {{providers}}", "nameEmpty": "O nome não pode estar vazio", "nameExists": "Já existe um perfil com este nome", "deleteProfile": "Excluir perfil", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index e4bd8876987..99b22f43ffd 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -230,6 +230,7 @@ "description": "Сохраняйте различные конфигурации API для быстрого переключения между провайдерами и настройками.", "apiProvider": "Провайдер API", "model": "Модель", + "autocompleteNotSupported": "⚠️ Автозаполнение поддерживается только следующими провайдерами: {{providers}}", "nameEmpty": "Имя не может быть пустым", "nameExists": "Профиль с таким именем уже существует", "deleteProfile": "Удалить профиль", diff --git a/webview-ui/src/i18n/locales/th/settings.json b/webview-ui/src/i18n/locales/th/settings.json index 606dc766c1f..2d19b8264b1 100644 --- a/webview-ui/src/i18n/locales/th/settings.json +++ b/webview-ui/src/i18n/locales/th/settings.json @@ -236,6 +236,7 @@ "description": "บันทึกการกำหนดค่า API ต่างๆ เพื่อสลับระหว่างผู้ให้บริการและการตั้งค่าต่างๆ ได้อย่างรวดเร็ว", "apiProvider": "ผู้ให้บริการ API", "model": "โมเดล", + "autocompleteNotSupported": "⚠️ การเติมข้อความอัตโนมัติรองรับเฉพาะผู้ให้บริการต่อไปนี้: {{providers}}", "nameEmpty": "ชื่อต้องไม่ว่างเปล่า", "nameExists": "มีโปรไฟล์ชื่อนี้อยู่แล้ว", "deleteProfile": "ลบโปรไฟล์", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 8f9bb5ba70d..bb4e9c147b8 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -231,6 +231,7 @@ "description": "Sağlayıcılar ve ayarlar arasında hızlıca geçiş yapmak için farklı API yapılandırmalarını kaydedin.", "apiProvider": "API Sağlayıcı", "model": "Model", + "autocompleteNotSupported": "⚠️ Otomatik tamamlama yalnızca şu sağlayıcılarla desteklenir: {{providers}}", "nameEmpty": "İsim boş olamaz", "nameExists": "Bu isme sahip bir profil zaten mevcut", "deleteProfile": "Profili sil", diff --git a/webview-ui/src/i18n/locales/uk/settings.json b/webview-ui/src/i18n/locales/uk/settings.json index 0f4a3ce1b7a..378deb7c942 100644 --- a/webview-ui/src/i18n/locales/uk/settings.json +++ b/webview-ui/src/i18n/locales/uk/settings.json @@ -242,6 +242,7 @@ "description": "Збережіть різні конфігурації API для швидкого перемикання між провайдерами та налаштуваннями.", "apiProvider": "Провайдер API", "model": "Модель", + "autocompleteNotSupported": "⚠️ Автодоповнення підтримується лише з наступними провайдерами: {{providers}}", "nameEmpty": "Ім'я не може бути порожнім", "nameExists": "Профіль із таким іменем уже існує", "deleteProfile": "Видалити профіль", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index de37986f5be..950afdc28f7 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -230,6 +230,7 @@ "description": "Lưu các cấu hình API khác nhau để nhanh chóng chuyển đổi giữa các nhà cung cấp và cài đặt.", "apiProvider": "Nhà cung cấp API", "model": "Mẫu", + "autocompleteNotSupported": "⚠️ Tự động hoàn thành chỉ được hỗ trợ với các nhà cung cấp sau: {{providers}}", "nameEmpty": "Tên không được để trống", "nameExists": "Đã tồn tại một hồ sơ với tên này", "deleteProfile": "Xóa hồ sơ", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 0f200d24bd2..b881cc3b437 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -231,6 +231,7 @@ "description": "保存多组API配置便于快速切换", "apiProvider": "API提供商", "model": "模型", + "autocompleteNotSupported": "⚠️ 自动完成仅支持以下提供商: {{providers}}", "nameEmpty": "名称不能为空", "nameExists": "已存在同名的配置文件", "deleteProfile": "删除配置文件", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 8b065ee52cb..90c07461c39 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -231,6 +231,7 @@ "description": "儲存不同的 API 設定以快速切換供應商和設定。", "apiProvider": "API 供應商", "model": "模型", + "autocompleteNotSupported": "⚠️ 自動完成僅支援以下提供者: {{providers}}", "nameEmpty": "名稱不能為空", "nameExists": "已存在同名的設定檔", "deleteProfile": "刪除設定檔", From 25d5e863b6f017a2130febde17a07cd91ded55b6 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 10:08:32 +0200 Subject: [PATCH 10/46] fix --- packages/types/src/provider-settings.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 0b80ad3246c..3598d3e987f 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -169,6 +169,10 @@ export const AUTOCOMPLETE_PROVIDER_MODELS = { openrouter: "mistralai/codestral-2508", } as const satisfies Record, string> +export const SUPPORTED_AUTOCOMPLETE_PROVIDERS = Object.keys( + AUTOCOMPLETE_PROVIDER_MODELS, +) as readonly (keyof typeof AUTOCOMPLETE_PROVIDER_MODELS)[] + /** * ProviderSettingsEntry */ From 7f5120c49e7cad117454d6062d6fee6e462216c4 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 10:21:15 +0200 Subject: [PATCH 11/46] simplify --- packages/types/src/provider-settings.ts | 4 ---- webview-ui/src/components/settings/ApiOptions.tsx | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 3598d3e987f..0b80ad3246c 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -169,10 +169,6 @@ export const AUTOCOMPLETE_PROVIDER_MODELS = { openrouter: "mistralai/codestral-2508", } as const satisfies Record, string> -export const SUPPORTED_AUTOCOMPLETE_PROVIDERS = Object.keys( - AUTOCOMPLETE_PROVIDER_MODELS, -) as readonly (keyof typeof AUTOCOMPLETE_PROVIDER_MODELS)[] - /** * ProviderSettingsEntry */ diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 78b3c93d6f2..0d0f8f3701b 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -8,7 +8,7 @@ import { type ProviderName, type ProviderSettings, DEFAULT_CONSECUTIVE_MISTAKE_LIMIT, - SUPPORTED_AUTOCOMPLETE_PROVIDERS, + AUTOCOMPLETE_PROVIDER_MODELS, openRouterDefaultModelId, requestyDefaultModelId, glamaDefaultModelId, @@ -496,12 +496,12 @@ const ApiOptions = ({ />
- {!(SUPPORTED_AUTOCOMPLETE_PROVIDERS as readonly string[]).includes(selectedProvider) && ( + {!Object.keys(AUTOCOMPLETE_PROVIDER_MODELS).includes(selectedProvider) && (
{t("settings:providers.autocompleteNotSupported", { - providers: SUPPORTED_AUTOCOMPLETE_PROVIDERS.join(", "), + providers: Object.keys(AUTOCOMPLETE_PROVIDER_MODELS).join(", "), })}
From 3bbafa6bed50db4bebd1a41805bb63e17031d586 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 10:42:26 +0200 Subject: [PATCH 12/46] make provider selection code simpler removed duplicate null checks by using types. also added test --- src/services/ghost/GhostModel.ts | 11 +-- .../ghost/__tests__/GhostModel.spec.ts | 92 +++++++++++++++++++ 2 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 src/services/ghost/__tests__/GhostModel.spec.ts diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 5fd9fbfdcb5..90c89d53c25 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -19,14 +19,11 @@ export class GhostModel { const profiles = await providerSettingsManager.listConfig() const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) const validProfiles = profiles - .filter((x) => x.apiProvider && x.apiProvider in AUTOCOMPLETE_PROVIDER_MODELS) + .filter( + (x): x is typeof x & { apiProvider: string } => + !!x.apiProvider && x.apiProvider in AUTOCOMPLETE_PROVIDER_MODELS, + ) .sort((a, b) => { - if (!a.apiProvider) { - return 1 - } - if (!b.apiProvider) { - return -1 - } return supportedProviders.indexOf(a.apiProvider) - supportedProviders.indexOf(b.apiProvider) }) diff --git a/src/services/ghost/__tests__/GhostModel.spec.ts b/src/services/ghost/__tests__/GhostModel.spec.ts new file mode 100644 index 00000000000..6cce9ffce91 --- /dev/null +++ b/src/services/ghost/__tests__/GhostModel.spec.ts @@ -0,0 +1,92 @@ +import { describe, it, expect, vi, beforeEach } from "vitest" +import { GhostModel } from "../GhostModel" +import { ProviderSettingsManager } from "../../../core/config/ProviderSettingsManager" +import { AUTOCOMPLETE_PROVIDER_MODELS } from "@roo-code/types" + +describe("GhostModel", () => { + let mockProviderSettingsManager: ProviderSettingsManager + + beforeEach(() => { + mockProviderSettingsManager = { + listConfig: vi.fn(), + getProfile: vi.fn(), + } as any + }) + + describe("reload", () => { + it("sorts profiles by supportedProviders index order", async () => { + const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) + const profiles = [ + { id: "3", name: "profile3", apiProvider: supportedProviders[2] }, + { id: "1", name: "profile1", apiProvider: supportedProviders[0] }, + { id: "2", name: "profile2", apiProvider: supportedProviders[1] }, + ] as any + + vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles) + vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({ + id: "1", + name: "profile1", + apiProvider: supportedProviders[0], + mistralApiKey: "test-key", + } as any) + + const model = new GhostModel() + await model.reload(mockProviderSettingsManager) + + expect(mockProviderSettingsManager.getProfile).toHaveBeenCalledWith({ id: "1" }) + }) + + it("filters out profiles without apiProvider", async () => { + const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) + const profiles = [ + { id: "1", name: "profile1", apiProvider: undefined }, + { id: "2", name: "profile2", apiProvider: supportedProviders[0] }, + ] as any + + vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles) + vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({ + id: "2", + name: "profile2", + apiProvider: supportedProviders[0], + mistralApiKey: "test-key", + } as any) + + const model = new GhostModel() + await model.reload(mockProviderSettingsManager) + + expect(mockProviderSettingsManager.getProfile).toHaveBeenCalledWith({ id: "2" }) + }) + + it("filters out profiles with unsupported apiProvider", async () => { + const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) + const profiles = [ + { id: "1", name: "profile1", apiProvider: "unsupported" }, + { id: "2", name: "profile2", apiProvider: supportedProviders[0] }, + ] as any + + vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles) + vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({ + id: "2", + name: "profile2", + apiProvider: supportedProviders[0], + mistralApiKey: "test-key", + } as any) + + const model = new GhostModel() + await model.reload(mockProviderSettingsManager) + + expect(mockProviderSettingsManager.getProfile).toHaveBeenCalledWith({ id: "2" }) + }) + + it("handles empty profile list", async () => { + vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue([]) + + const model = new GhostModel() + await model.reload(mockProviderSettingsManager) + + expect(mockProviderSettingsManager.getProfile).not.toHaveBeenCalled() + expect(model.loaded).toBe(true) + expect(model.hasValidCredentials()).toBe(false) + }) + }) +}) From 97d97f6975f4977c066ca3842449e8581fdde276 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 10:47:37 +0200 Subject: [PATCH 13/46] remove hardcoding --- src/services/ghost/GhostModel.ts | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 90c89d53c25..a42366b322c 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -1,4 +1,4 @@ -import { AUTOCOMPLETE_PROVIDER_MODELS } from "@roo-code/types" +import { AUTOCOMPLETE_PROVIDER_MODELS, modelIdKeysByProvider } from "@roo-code/types" import { ApiHandler, buildApiHandler } from "../../api" import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager" import { OpenRouterHandler } from "../../api/providers" @@ -32,20 +32,10 @@ export class GhostModel { const profile = await providerSettingsManager.getProfile({ id: selectedProfile.id, }) - const profileProvider = profile.apiProvider - let modelDefinition = {} - if (profileProvider === "kilocode") { - modelDefinition = { - kilocodeModel: AUTOCOMPLETE_PROVIDER_MODELS.kilocode, - } - } else if (profileProvider === "openrouter") { - modelDefinition = { - openRouterModelId: AUTOCOMPLETE_PROVIDER_MODELS.openrouter, - } - } else if (profileProvider === "mistral") { - modelDefinition = { - apiModelId: AUTOCOMPLETE_PROVIDER_MODELS.mistral, - } + const profileProvider = profile.apiProvider as keyof typeof AUTOCOMPLETE_PROVIDER_MODELS + const modelIdKey = modelIdKeysByProvider[profileProvider] + const modelDefinition = { + [modelIdKey]: AUTOCOMPLETE_PROVIDER_MODELS[profileProvider], } this.apiHandler = buildApiHandler({ ...profile, From 32a2de0300008b1a313024c5438ec71bbf31e9e5 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 10:54:52 +0200 Subject: [PATCH 14/46] switch over which list we are looping --- src/services/ghost/GhostModel.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index a42366b322c..fa4cf63b6f7 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -18,16 +18,18 @@ export class GhostModel { public async reload(providerSettingsManager: ProviderSettingsManager) { const profiles = await providerSettingsManager.listConfig() const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) - const validProfiles = profiles - .filter( - (x): x is typeof x & { apiProvider: string } => - !!x.apiProvider && x.apiProvider in AUTOCOMPLETE_PROVIDER_MODELS, + + let selectedProfile = null + for (const provider of supportedProviders) { + const profile = profiles.find( + (x): x is typeof x & { apiProvider: string } => !!x.apiProvider && x.apiProvider === provider, ) - .sort((a, b) => { - return supportedProviders.indexOf(a.apiProvider) - supportedProviders.indexOf(b.apiProvider) - }) + if (profile) { + selectedProfile = profile + break + } + } - const selectedProfile = validProfiles[0] || null if (selectedProfile) { const profile = await providerSettingsManager.getProfile({ id: selectedProfile.id, From 5b0e9b215f15e737912adf6a6136add2e93635f4 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 11:00:11 +0200 Subject: [PATCH 15/46] simplify --- src/services/ghost/GhostModel.ts | 36 ++++++++++++++------------------ 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index fa4cf63b6f7..51092d14947 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -17,32 +17,28 @@ export class GhostModel { public async reload(providerSettingsManager: ProviderSettingsManager) { const profiles = await providerSettingsManager.listConfig() - const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) + const supportedProviders = Object.keys( + AUTOCOMPLETE_PROVIDER_MODELS, + ) as (keyof typeof AUTOCOMPLETE_PROVIDER_MODELS)[] - let selectedProfile = null for (const provider of supportedProviders) { - const profile = profiles.find( + const selectedProfile = profiles.find( (x): x is typeof x & { apiProvider: string } => !!x.apiProvider && x.apiProvider === provider, ) - if (profile) { - selectedProfile = profile - break - } - } + if (selectedProfile) { + const profile = await providerSettingsManager.getProfile({ + id: selectedProfile.id, + }) + const modelDefinition = { + [modelIdKeysByProvider[provider]]: AUTOCOMPLETE_PROVIDER_MODELS[provider], + } + this.apiHandler = buildApiHandler({ + ...profile, + ...modelDefinition, + }) - if (selectedProfile) { - const profile = await providerSettingsManager.getProfile({ - id: selectedProfile.id, - }) - const profileProvider = profile.apiProvider as keyof typeof AUTOCOMPLETE_PROVIDER_MODELS - const modelIdKey = modelIdKeysByProvider[profileProvider] - const modelDefinition = { - [modelIdKey]: AUTOCOMPLETE_PROVIDER_MODELS[profileProvider], + break } - this.apiHandler = buildApiHandler({ - ...profile, - ...modelDefinition, - }) } if (this.apiHandler instanceof OpenRouterHandler) { From 3194961064dc1e93ae30c099f1a0be705bc772de Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 11:11:23 +0200 Subject: [PATCH 16/46] show provider name in autocomplete tooltip --- src/i18n/locales/en/kilocode.json | 1 + src/services/ghost/GhostModel.ts | 58 ++++++++++++++++++- src/services/ghost/GhostProvider.ts | 9 +++ src/services/ghost/GhostStatusBar.ts | 5 ++ .../ghost/__tests__/GhostModel.spec.ts | 45 ++++++++++++++ 5 files changed, 117 insertions(+), 1 deletion(-) diff --git a/src/i18n/locales/en/kilocode.json b/src/i18n/locales/en/kilocode.json index a9c72d0c2a4..8ddcd0656e8 100644 --- a/src/i18n/locales/en/kilocode.json +++ b/src/i18n/locales/en/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "A valid token must be set to use Autocomplete", "lastCompletion": "Last suggestion:", "sessionTotal": "Session total cost:", + "provider": "Provider:", "model": "Model:" }, "cost": { diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 51092d14947..c2de7cf9d5f 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -6,6 +6,7 @@ import { ApiStreamChunk } from "../../api/transform/stream" export class GhostModel { private apiHandler: ApiHandler | null = null + private provider: string | null = null public loaded = false constructor(apiHandler: ApiHandler | null = null) { @@ -36,6 +37,7 @@ export class GhostModel { ...profile, ...modelDefinition, }) + this.provider = provider break } @@ -111,10 +113,64 @@ export class GhostModel { if (!this.apiHandler) { return null } - // Extract model name from API handler return this.apiHandler.getModel().id ?? "unknown" } + public getProviderDisplayName(): string | null { + if (!this.provider) { + return null + } + return this.formatProviderName(this.provider) + } + + private formatProviderName(provider: string): string { + const providerDisplayNames: Record = { + anthropic: "Anthropic", + openai: "OpenAI", + "openai-native": "OpenAI", + openrouter: "OpenRouter", + bedrock: "AWS Bedrock", + vertex: "Google Vertex AI", + ollama: "Ollama", + lmstudio: "LM Studio", + gemini: "Google Gemini", + "gemini-cli": "Google Gemini CLI", + deepseek: "DeepSeek", + doubao: "Doubao", + "qwen-code": "Qwen Code", + moonshot: "Moonshot", + "vscode-lm": "VS Code LM", + mistral: "Mistral AI", + unbound: "Unbound", + requesty: "Requesty", + "human-relay": "Human Relay", + "fake-ai": "Fake AI", + xai: "xAI", + groq: "Groq", + deepinfra: "DeepInfra", + huggingface: "Hugging Face", + chutes: "Chutes", + litellm: "LiteLLM", + cerebras: "Cerebras", + sambanova: "SambaNova", + zai: "ZAI", + fireworks: "Fireworks AI", + synthetic: "Synthetic", + "io-intelligence": "IO Intelligence", + roo: "Roo", + featherless: "Featherless", + "vercel-ai-gateway": "Vercel AI Gateway", + ovhcloud: "OVHcloud", + kilocode: "Kilo Code", + "kilocode-openrouter": "Kilo Code (OpenRouter)", + "virtual-quota-fallback": "Virtual Quota Fallback", + glama: "Glama", + "claude-code": "Claude Code", + } + + return providerDisplayNames[provider] || provider + } + public hasValidCredentials(): boolean { return this.apiHandler !== null && this.loaded } diff --git a/src/services/ghost/GhostProvider.ts b/src/services/ghost/GhostProvider.ts index bd06e533542..1941578030b 100644 --- a/src/services/ghost/GhostProvider.ts +++ b/src/services/ghost/GhostProvider.ts @@ -586,6 +586,7 @@ export class GhostProvider { this.statusBar = new GhostStatusBar({ enabled: false, model: "loading...", + provider: "loading...", hasValidToken: false, totalSessionCost: 0, lastCompletionCost: 0, @@ -599,6 +600,13 @@ export class GhostProvider { return this.model.getModelName() ?? "unknown" } + private getCurrentProviderName(): string { + if (!this.model.loaded) { + return "loading..." + } + return this.model.getProviderDisplayName() ?? "unknown" + } + private hasValidApiToken(): boolean { return this.model.loaded && this.model.hasValidCredentials() } @@ -617,6 +625,7 @@ export class GhostProvider { this.statusBar?.update({ enabled: this.settings?.enableAutoTrigger, model: this.getCurrentModelName(), + provider: this.getCurrentProviderName(), hasValidToken: this.hasValidApiToken(), totalSessionCost: this.sessionCost, lastCompletionCost: this.lastCompletionCost, diff --git a/src/services/ghost/GhostStatusBar.ts b/src/services/ghost/GhostStatusBar.ts index 8c3e50fecf7..c2fc2ca24ff 100644 --- a/src/services/ghost/GhostStatusBar.ts +++ b/src/services/ghost/GhostStatusBar.ts @@ -4,6 +4,7 @@ import { t } from "../../i18n" interface GhostStatusBarStateProps { enabled?: boolean model?: string + provider?: string hasValidToken?: boolean totalSessionCost?: number lastCompletionCost?: number @@ -13,6 +14,7 @@ export class GhostStatusBar { statusBar: vscode.StatusBarItem enabled: boolean model: string + provider: string hasValidToken: boolean totalSessionCost?: number lastCompletionCost?: number @@ -21,6 +23,7 @@ export class GhostStatusBar { this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100) this.enabled = params.enabled || false this.model = params.model || "default" + this.provider = params.provider || "default" this.hasValidToken = params.hasValidToken || false this.totalSessionCost = params.totalSessionCost this.lastCompletionCost = params.lastCompletionCost @@ -55,6 +58,7 @@ export class GhostStatusBar { public update(params: GhostStatusBarStateProps) { this.enabled = params.enabled !== undefined ? params.enabled : this.enabled this.model = params.model !== undefined ? params.model : this.model + this.provider = params.provider !== undefined ? params.provider : this.provider this.hasValidToken = params.hasValidToken !== undefined ? params.hasValidToken : this.hasValidToken this.totalSessionCost = params.totalSessionCost !== undefined ? params.totalSessionCost : this.totalSessionCost this.lastCompletionCost = @@ -83,6 +87,7 @@ export class GhostStatusBar { ${t("kilocode:ghost.statusBar.tooltip.basic")} • ${t("kilocode:ghost.statusBar.tooltip.lastCompletion")} $${lastCompletionCostFormatted} • ${t("kilocode:ghost.statusBar.tooltip.sessionTotal")} ${totalCostFormatted} +• ${t("kilocode:ghost.statusBar.tooltip.provider")} ${this.provider} • ${t("kilocode:ghost.statusBar.tooltip.model")} ${this.model}\ ` } diff --git a/src/services/ghost/__tests__/GhostModel.spec.ts b/src/services/ghost/__tests__/GhostModel.spec.ts index 6cce9ffce91..9b73e347a9b 100644 --- a/src/services/ghost/__tests__/GhostModel.spec.ts +++ b/src/services/ghost/__tests__/GhostModel.spec.ts @@ -89,4 +89,49 @@ describe("GhostModel", () => { expect(model.hasValidCredentials()).toBe(false) }) }) + + describe("getProviderDisplayName", () => { + it("returns null when no provider is loaded", () => { + const model = new GhostModel() + expect(model.getProviderDisplayName()).toBeNull() + }) + + it("returns formatted provider name when provider is loaded", async () => { + const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) + const profiles = [{ id: "1", name: "profile1", apiProvider: supportedProviders[0] }] as any + + vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles) + vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({ + id: "1", + name: "profile1", + apiProvider: supportedProviders[0], + mistralApiKey: "test-key", + } as any) + + const model = new GhostModel() + await model.reload(mockProviderSettingsManager) + + const providerName = model.getProviderDisplayName() + expect(providerName).toBeTruthy() + expect(typeof providerName).toBe("string") + expect(providerName).not.toBe(supportedProviders[0]) + }) + + it("formats known provider names correctly", async () => { + const profiles = [{ id: "1", name: "profile1", apiProvider: "mistral" }] as any + + vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles) + vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({ + id: "1", + name: "profile1", + apiProvider: "mistral", + mistralApiKey: "test-key", + } as any) + + const model = new GhostModel() + await model.reload(mockProviderSettingsManager) + + expect(model.getProviderDisplayName()).toBe("Mistral AI") + }) + }) }) From d3e633797a843652881770aecf57442de17ae3bf Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 11:35:44 +0200 Subject: [PATCH 17/46] remove hardcoded list --- src/services/ghost/GhostModel.ts | 60 ++++--------------- .../ghost/__tests__/GhostModel.spec.ts | 20 +------ 2 files changed, 12 insertions(+), 68 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index c2de7cf9d5f..7cdfeaf25df 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -6,7 +6,7 @@ import { ApiStreamChunk } from "../../api/transform/stream" export class GhostModel { private apiHandler: ApiHandler | null = null - private provider: string | null = null + private providerDisplayName: string | null = null public loaded = false constructor(apiHandler: ApiHandler | null = null) { @@ -37,7 +37,7 @@ export class GhostModel { ...profile, ...modelDefinition, }) - this.provider = provider + this.providerDisplayName = this.extractProviderName() break } @@ -117,58 +117,20 @@ export class GhostModel { } public getProviderDisplayName(): string | null { - if (!this.provider) { + return this.providerDisplayName + } + + private extractProviderName(): string | null { + if (!this.apiHandler) { return null } - return this.formatProviderName(this.provider) - } - private formatProviderName(provider: string): string { - const providerDisplayNames: Record = { - anthropic: "Anthropic", - openai: "OpenAI", - "openai-native": "OpenAI", - openrouter: "OpenRouter", - bedrock: "AWS Bedrock", - vertex: "Google Vertex AI", - ollama: "Ollama", - lmstudio: "LM Studio", - gemini: "Google Gemini", - "gemini-cli": "Google Gemini CLI", - deepseek: "DeepSeek", - doubao: "Doubao", - "qwen-code": "Qwen Code", - moonshot: "Moonshot", - "vscode-lm": "VS Code LM", - mistral: "Mistral AI", - unbound: "Unbound", - requesty: "Requesty", - "human-relay": "Human Relay", - "fake-ai": "Fake AI", - xai: "xAI", - groq: "Groq", - deepinfra: "DeepInfra", - huggingface: "Hugging Face", - chutes: "Chutes", - litellm: "LiteLLM", - cerebras: "Cerebras", - sambanova: "SambaNova", - zai: "ZAI", - fireworks: "Fireworks AI", - synthetic: "Synthetic", - "io-intelligence": "IO Intelligence", - roo: "Roo", - featherless: "Featherless", - "vercel-ai-gateway": "Vercel AI Gateway", - ovhcloud: "OVHcloud", - kilocode: "Kilo Code", - "kilocode-openrouter": "Kilo Code (OpenRouter)", - "virtual-quota-fallback": "Virtual Quota Fallback", - glama: "Glama", - "claude-code": "Claude Code", + const handler = this.apiHandler as any + if (handler.providerName && typeof handler.providerName === "string") { + return handler.providerName } - return providerDisplayNames[provider] || provider + return "Unknown Provider" } public hasValidCredentials(): boolean { diff --git a/src/services/ghost/__tests__/GhostModel.spec.ts b/src/services/ghost/__tests__/GhostModel.spec.ts index 9b73e347a9b..94fc82db949 100644 --- a/src/services/ghost/__tests__/GhostModel.spec.ts +++ b/src/services/ghost/__tests__/GhostModel.spec.ts @@ -96,7 +96,7 @@ describe("GhostModel", () => { expect(model.getProviderDisplayName()).toBeNull() }) - it("returns formatted provider name when provider is loaded", async () => { + it("returns provider name from API handler when provider is loaded", async () => { const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) const profiles = [{ id: "1", name: "profile1", apiProvider: supportedProviders[0] }] as any @@ -114,24 +114,6 @@ describe("GhostModel", () => { const providerName = model.getProviderDisplayName() expect(providerName).toBeTruthy() expect(typeof providerName).toBe("string") - expect(providerName).not.toBe(supportedProviders[0]) - }) - - it("formats known provider names correctly", async () => { - const profiles = [{ id: "1", name: "profile1", apiProvider: "mistral" }] as any - - vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles) - vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({ - id: "1", - name: "profile1", - apiProvider: "mistral", - mistralApiKey: "test-key", - } as any) - - const model = new GhostModel() - await model.reload(mockProviderSettingsManager) - - expect(model.getProviderDisplayName()).toBe("Mistral AI") }) }) }) From 567869fca594261dd02c278af9c4b8f021398597 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 12:10:04 +0200 Subject: [PATCH 18/46] do not say we have loaded when we havent add proper cleanup instead of sticking to the old profile if we cant find one --- src/services/ghost/GhostModel.ts | 39 +++++++++++-------- .../ghost/__tests__/GhostModel.spec.ts | 22 ++++++++++- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 7cdfeaf25df..79fdceaf89e 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -15,39 +15,46 @@ export class GhostModel { this.loaded = true } } + private cleanup(): void { + this.apiHandler = null + this.providerDisplayName = null + this.loaded = false + } - public async reload(providerSettingsManager: ProviderSettingsManager) { + public async reload(providerSettingsManager: ProviderSettingsManager): Promise { const profiles = await providerSettingsManager.listConfig() - const supportedProviders = Object.keys( - AUTOCOMPLETE_PROVIDER_MODELS, - ) as (keyof typeof AUTOCOMPLETE_PROVIDER_MODELS)[] + const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) as Array< + keyof typeof AUTOCOMPLETE_PROVIDER_MODELS + > + + this.cleanup() for (const provider of supportedProviders) { const selectedProfile = profiles.find( - (x): x is typeof x & { apiProvider: string } => !!x.apiProvider && x.apiProvider === provider, + (x): x is typeof x & { apiProvider: string } => x?.apiProvider === provider, ) if (selectedProfile) { const profile = await providerSettingsManager.getProfile({ id: selectedProfile.id, }) - const modelDefinition = { - [modelIdKeysByProvider[provider]]: AUTOCOMPLETE_PROVIDER_MODELS[provider], - } + this.apiHandler = buildApiHandler({ ...profile, - ...modelDefinition, + [modelIdKeysByProvider[provider]]: AUTOCOMPLETE_PROVIDER_MODELS[provider], }) - this.providerDisplayName = this.extractProviderName() - break - } - } + if (this.apiHandler instanceof OpenRouterHandler) { + await this.apiHandler.fetchModel() + } - if (this.apiHandler instanceof OpenRouterHandler) { - await this.apiHandler.fetchModel() + this.providerDisplayName = this.extractProviderName() + this.loaded = true + return true + } } - this.loaded = true + this.loaded = false + return false } /** diff --git a/src/services/ghost/__tests__/GhostModel.spec.ts b/src/services/ghost/__tests__/GhostModel.spec.ts index 94fc82db949..53fa6db35ae 100644 --- a/src/services/ghost/__tests__/GhostModel.spec.ts +++ b/src/services/ghost/__tests__/GhostModel.spec.ts @@ -82,11 +82,31 @@ describe("GhostModel", () => { vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue([]) const model = new GhostModel() - await model.reload(mockProviderSettingsManager) + const result = await model.reload(mockProviderSettingsManager) expect(mockProviderSettingsManager.getProfile).not.toHaveBeenCalled() expect(model.loaded).toBe(true) expect(model.hasValidCredentials()).toBe(false) + expect(result).toBe(false) + }) + + it("returns true when profile found", async () => { + const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) + const profiles = [{ id: "1", name: "profile1", apiProvider: supportedProviders[0] }] as any + + vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles) + vi.mocked(mockProviderSettingsManager.getProfile).mockResolvedValue({ + id: "1", + name: "profile1", + apiProvider: supportedProviders[0], + mistralApiKey: "test-key", + } as any) + + const model = new GhostModel() + const result = await model.reload(mockProviderSettingsManager) + + expect(result).toBe(true) + expect(model.loaded).toBe(true) }) }) From e1f945b9c455a9fb8456d389d8bc8de04ea73544 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 13:11:27 +0200 Subject: [PATCH 19/46] add translation --- src/i18n/locales/ar/kilocode.json | 1 + src/i18n/locales/ca/kilocode.json | 1 + src/i18n/locales/cs/kilocode.json | 1 + src/i18n/locales/de/kilocode.json | 1 + src/i18n/locales/es/kilocode.json | 1 + src/i18n/locales/fr/kilocode.json | 1 + src/i18n/locales/hi/kilocode.json | 1 + src/i18n/locales/id/kilocode.json | 1 + src/i18n/locales/it/kilocode.json | 1 + src/i18n/locales/ja/kilocode.json | 1 + src/i18n/locales/ko/kilocode.json | 1 + src/i18n/locales/nl/kilocode.json | 1 + src/i18n/locales/pl/kilocode.json | 1 + src/i18n/locales/pt-BR/kilocode.json | 1 + src/i18n/locales/ru/kilocode.json | 1 + src/i18n/locales/th/kilocode.json | 1 + src/i18n/locales/tr/kilocode.json | 1 + src/i18n/locales/uk/kilocode.json | 1 + src/i18n/locales/vi/kilocode.json | 1 + src/i18n/locales/zh-CN/kilocode.json | 1 + src/i18n/locales/zh-TW/kilocode.json | 1 + 21 files changed, 21 insertions(+) diff --git a/src/i18n/locales/ar/kilocode.json b/src/i18n/locales/ar/kilocode.json index fbd5a357a21..0e06037a1ba 100644 --- a/src/i18n/locales/ar/kilocode.json +++ b/src/i18n/locales/ar/kilocode.json @@ -77,6 +77,7 @@ "tokenError": "يجب تعيين رمز صالح لاستخدام الإكمال التلقائي", "lastCompletion": "آخر اقتراح:", "sessionTotal": "إجمالي تكلفة الجلسة:", + "provider": "المزود:", "model": "النموذج:" }, "cost": { diff --git a/src/i18n/locales/ca/kilocode.json b/src/i18n/locales/ca/kilocode.json index 3ac7c47c957..77893fa76de 100644 --- a/src/i18n/locales/ca/kilocode.json +++ b/src/i18n/locales/ca/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "S'ha d'establir un token vàlid per utilitzar Autocomplete", "lastCompletion": "Últim suggeriment:", "sessionTotal": "Cost total de la sessió:", + "provider": "Proveïdor:", "model": "Model:" }, "cost": { diff --git a/src/i18n/locales/cs/kilocode.json b/src/i18n/locales/cs/kilocode.json index 210aec58f1f..d9bb3fc866b 100644 --- a/src/i18n/locales/cs/kilocode.json +++ b/src/i18n/locales/cs/kilocode.json @@ -79,6 +79,7 @@ "tokenError": "Pro použití Autocomplete musí být nastaven platný token", "lastCompletion": "Poslední návrh:", "sessionTotal": "Celkové náklady relace:", + "provider": "Poskytovatel:", "model": "Model:" }, "cost": { diff --git a/src/i18n/locales/de/kilocode.json b/src/i18n/locales/de/kilocode.json index f29c21504e2..2d923c98831 100644 --- a/src/i18n/locales/de/kilocode.json +++ b/src/i18n/locales/de/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Ein gültiger Token muss gesetzt werden, um Autocomplete zu verwenden", "lastCompletion": "Letzter Vorschlag:", "sessionTotal": "Sitzungsgesamtkosten:", + "provider": "Anbieter:", "model": "Modell:" }, "cost": { diff --git a/src/i18n/locales/es/kilocode.json b/src/i18n/locales/es/kilocode.json index 511e67de6a8..5471dc8d0ea 100644 --- a/src/i18n/locales/es/kilocode.json +++ b/src/i18n/locales/es/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Se debe establecer un token válido para usar Autocomplete", "lastCompletion": "Última sugerencia:", "sessionTotal": "Costo total de la sesión:", + "provider": "Proveedor:", "model": "Modelo:" }, "cost": { diff --git a/src/i18n/locales/fr/kilocode.json b/src/i18n/locales/fr/kilocode.json index 3559aa06431..0218c6af9e5 100644 --- a/src/i18n/locales/fr/kilocode.json +++ b/src/i18n/locales/fr/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Un token valide doit être défini pour utiliser Autocomplete", "lastCompletion": "Dernière suggestion :", "sessionTotal": "Coût total de la session :", + "provider": "Fournisseur:", "model": "Modèle :" }, "cost": { diff --git a/src/i18n/locales/hi/kilocode.json b/src/i18n/locales/hi/kilocode.json index 1e53c8a82ad..8c8d802b212 100644 --- a/src/i18n/locales/hi/kilocode.json +++ b/src/i18n/locales/hi/kilocode.json @@ -79,6 +79,7 @@ "tokenError": "Autocomplete का उपयोग करने के लिए एक वैध टोकन सेट करना होगा", "lastCompletion": "अंतिम सुझाव:", "sessionTotal": "सत्र की कुल लागत:", + "provider": "प्रदाता:", "model": "मॉडल:" }, "cost": { diff --git a/src/i18n/locales/id/kilocode.json b/src/i18n/locales/id/kilocode.json index d43fe5f8651..f08ba948a43 100644 --- a/src/i18n/locales/id/kilocode.json +++ b/src/i18n/locales/id/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Token yang valid harus diatur untuk menggunakan Autocomplete", "lastCompletion": "Saran terakhir:", "sessionTotal": "Total biaya sesi:", + "provider": "Penyedia:", "model": "Model:" }, "cost": { diff --git a/src/i18n/locales/it/kilocode.json b/src/i18n/locales/it/kilocode.json index 105fe5fdb20..0191498a2ec 100644 --- a/src/i18n/locales/it/kilocode.json +++ b/src/i18n/locales/it/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Deve essere impostato un token valido per usare Autocomplete", "lastCompletion": "Ultimo suggerimento:", "sessionTotal": "Costo totale della sessione:", + "provider": "Provider:", "model": "Modello:" }, "cost": { diff --git a/src/i18n/locales/ja/kilocode.json b/src/i18n/locales/ja/kilocode.json index 6b1797a7610..baad36f6838 100644 --- a/src/i18n/locales/ja/kilocode.json +++ b/src/i18n/locales/ja/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Autocompleteを使用するには有効なトークンを設定する必要があります", "lastCompletion": "最後の提案:", "sessionTotal": "セッション合計コスト:", + "provider": "プロバイダー:", "model": "モデル:" }, "cost": { diff --git a/src/i18n/locales/ko/kilocode.json b/src/i18n/locales/ko/kilocode.json index e68aba9bd8a..f069c82a6f5 100644 --- a/src/i18n/locales/ko/kilocode.json +++ b/src/i18n/locales/ko/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Autocomplete를 사용하려면 유효한 토큰을 설정해야 합니다", "lastCompletion": "마지막 제안:", "sessionTotal": "세션 총 비용:", + "provider": "제공업체:", "model": "모델:" }, "cost": { diff --git a/src/i18n/locales/nl/kilocode.json b/src/i18n/locales/nl/kilocode.json index 106360dc285..8296608c60a 100644 --- a/src/i18n/locales/nl/kilocode.json +++ b/src/i18n/locales/nl/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Een geldig token moet worden ingesteld om Autocomplete te gebruiken", "lastCompletion": "Laatste suggestie:", "sessionTotal": "Totale sessiekosten:", + "provider": "Provider:", "model": "Model:" }, "cost": { diff --git a/src/i18n/locales/pl/kilocode.json b/src/i18n/locales/pl/kilocode.json index 22bc4aa445d..e76f9e1e354 100644 --- a/src/i18n/locales/pl/kilocode.json +++ b/src/i18n/locales/pl/kilocode.json @@ -79,6 +79,7 @@ "tokenError": "Aby używać Autocomplete, musi być ustawiony ważny token", "lastCompletion": "Ostatnia sugestia:", "sessionTotal": "Całkowity koszt sesji:", + "provider": "Dostawca:", "model": "Model:" }, "cost": { diff --git a/src/i18n/locales/pt-BR/kilocode.json b/src/i18n/locales/pt-BR/kilocode.json index 821627c6e8c..a3494e0bbcc 100644 --- a/src/i18n/locales/pt-BR/kilocode.json +++ b/src/i18n/locales/pt-BR/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Um token válido deve ser definido para usar o Autocomplete", "lastCompletion": "Última sugestão:", "sessionTotal": "Custo total da sessão:", + "provider": "Provedor:", "model": "Modelo:" }, "cost": { diff --git a/src/i18n/locales/ru/kilocode.json b/src/i18n/locales/ru/kilocode.json index d538f6f9947..3fbc0bc4149 100644 --- a/src/i18n/locales/ru/kilocode.json +++ b/src/i18n/locales/ru/kilocode.json @@ -68,6 +68,7 @@ "tokenError": "Для использования Autocomplete необходимо установить действительный токен", "lastCompletion": "Последнее предложение:", "sessionTotal": "Общая стоимость сессии:", + "provider": "Провайдер:", "model": "Модель:" }, "cost": { diff --git a/src/i18n/locales/th/kilocode.json b/src/i18n/locales/th/kilocode.json index d9fb77c77a7..680dd38f0de 100644 --- a/src/i18n/locales/th/kilocode.json +++ b/src/i18n/locales/th/kilocode.json @@ -79,6 +79,7 @@ "tokenError": "ต้องตั้งค่าโทเค็นที่ถูกต้องเพื่อใช้ Autocomplete", "lastCompletion": "คำแนะนำล่าสุด:", "sessionTotal": "ค่าใช้จ่ายรวมของเซสชัน:", + "provider": "ผู้ให้บริการ:", "model": "โมเดล:" }, "cost": { diff --git a/src/i18n/locales/tr/kilocode.json b/src/i18n/locales/tr/kilocode.json index d5bc68bdf48..4d52a9b5e00 100644 --- a/src/i18n/locales/tr/kilocode.json +++ b/src/i18n/locales/tr/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Autocomplete kullanmak için geçerli bir token ayarlanmalı", "lastCompletion": "Son öneri:", "sessionTotal": "Oturum toplam maliyeti:", + "provider": "Sağlayıcı:", "model": "Model:" }, "cost": { diff --git a/src/i18n/locales/uk/kilocode.json b/src/i18n/locales/uk/kilocode.json index 7a74da1b72b..0eedcea7650 100644 --- a/src/i18n/locales/uk/kilocode.json +++ b/src/i18n/locales/uk/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Для використання Autocomplete потрібно встановити дійсний токен", "lastCompletion": "Остання пропозиція:", "sessionTotal": "Загальна вартість сесії:", + "provider": "Провайдер:", "model": "Модель:" }, "cost": { diff --git a/src/i18n/locales/vi/kilocode.json b/src/i18n/locales/vi/kilocode.json index 44e843e77c3..8397725927e 100644 --- a/src/i18n/locales/vi/kilocode.json +++ b/src/i18n/locales/vi/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "Phải đặt token hợp lệ để sử dụng Autocomplete", "lastCompletion": "Gợi ý cuối cùng:", "sessionTotal": "Tổng chi phí phiên:", + "provider": "Nhà cung cấp:", "model": "Mô hình:" }, "cost": { diff --git a/src/i18n/locales/zh-CN/kilocode.json b/src/i18n/locales/zh-CN/kilocode.json index afe9d323fe5..ee461b9de38 100644 --- a/src/i18n/locales/zh-CN/kilocode.json +++ b/src/i18n/locales/zh-CN/kilocode.json @@ -79,6 +79,7 @@ "tokenError": "必须设置有效 Token 才能使用 Autocomplete", "lastCompletion": "最后建议:", "sessionTotal": "会话总费用:", + "provider": "提供商:", "model": "模型:" }, "cost": { diff --git a/src/i18n/locales/zh-TW/kilocode.json b/src/i18n/locales/zh-TW/kilocode.json index 78b516060f3..798553dfddd 100644 --- a/src/i18n/locales/zh-TW/kilocode.json +++ b/src/i18n/locales/zh-TW/kilocode.json @@ -73,6 +73,7 @@ "tokenError": "必須設定有效 Token 才能使用 Autocomplete", "lastCompletion": "最後建議:", "sessionTotal": "工作階段總費用:", + "provider": "提供者:", "model": "模型:" }, "cost": { From 09444a12a7d6fab9e4729a9c827520743e4f2bdc Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 13:16:03 +0200 Subject: [PATCH 20/46] fix test --- src/services/ghost/__tests__/GhostModel.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/ghost/__tests__/GhostModel.spec.ts b/src/services/ghost/__tests__/GhostModel.spec.ts index 53fa6db35ae..95d6505fc1c 100644 --- a/src/services/ghost/__tests__/GhostModel.spec.ts +++ b/src/services/ghost/__tests__/GhostModel.spec.ts @@ -85,7 +85,6 @@ describe("GhostModel", () => { const result = await model.reload(mockProviderSettingsManager) expect(mockProviderSettingsManager.getProfile).not.toHaveBeenCalled() - expect(model.loaded).toBe(true) expect(model.hasValidCredentials()).toBe(false) expect(result).toBe(false) }) From f20b4e33d79a88270029df34dc1c02a16202ca5a Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 13:45:17 +0200 Subject: [PATCH 21/46] inline --- src/services/ghost/GhostModel.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 79fdceaf89e..fd6e2e2a09b 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -47,7 +47,13 @@ export class GhostModel { await this.apiHandler.fetchModel() } - this.providerDisplayName = this.extractProviderName() + const handler = this.apiHandler as any + if (handler.providerName && typeof handler.providerName === "string") { + this.providerDisplayName = handler.providerName + } else { + this.providerDisplayName = "Unknown Provider" + } + this.loaded = true return true } @@ -127,19 +133,6 @@ export class GhostModel { return this.providerDisplayName } - private extractProviderName(): string | null { - if (!this.apiHandler) { - return null - } - - const handler = this.apiHandler as any - if (handler.providerName && typeof handler.providerName === "string") { - return handler.providerName - } - - return "Unknown Provider" - } - public hasValidCredentials(): boolean { return this.apiHandler !== null && this.loaded } From 10c151a8d7b311048717f177bd0420891acb622b Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 14:04:32 +0200 Subject: [PATCH 22/46] extract method --- src/services/ghost/GhostModel.ts | 49 ++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index fd6e2e2a09b..5ac65abb796 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -1,4 +1,4 @@ -import { AUTOCOMPLETE_PROVIDER_MODELS, modelIdKeysByProvider } from "@roo-code/types" +import { AUTOCOMPLETE_PROVIDER_MODELS, modelIdKeysByProvider, ProviderSettingsEntry } from "@roo-code/types" import { ApiHandler, buildApiHandler } from "../../api" import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager" import { OpenRouterHandler } from "../../api/providers" @@ -34,26 +34,7 @@ export class GhostModel { (x): x is typeof x & { apiProvider: string } => x?.apiProvider === provider, ) if (selectedProfile) { - const profile = await providerSettingsManager.getProfile({ - id: selectedProfile.id, - }) - - this.apiHandler = buildApiHandler({ - ...profile, - [modelIdKeysByProvider[provider]]: AUTOCOMPLETE_PROVIDER_MODELS[provider], - }) - - if (this.apiHandler instanceof OpenRouterHandler) { - await this.apiHandler.fetchModel() - } - - const handler = this.apiHandler as any - if (handler.providerName && typeof handler.providerName === "string") { - this.providerDisplayName = handler.providerName - } else { - this.providerDisplayName = "Unknown Provider" - } - + this.loadProfile(providerSettingsManager, selectedProfile, provider) this.loaded = true return true } @@ -63,6 +44,32 @@ export class GhostModel { return false } + public async loadProfile( + providerSettingsManager: ProviderSettingsManager, + selectedProfile: ProviderSettingsEntry, + provider: keyof typeof AUTOCOMPLETE_PROVIDER_MODELS, + ): Promise { + const profile = await providerSettingsManager.getProfile({ + id: selectedProfile.id, + }) + + this.apiHandler = buildApiHandler({ + ...profile, + [modelIdKeysByProvider[provider]]: AUTOCOMPLETE_PROVIDER_MODELS[provider], + }) + + if (this.apiHandler instanceof OpenRouterHandler) { + await this.apiHandler.fetchModel() + } + + const handler = this.apiHandler as any + if (handler.providerName && typeof handler.providerName === "string") { + this.providerDisplayName = handler.providerName + } else { + this.providerDisplayName = "Unknown Provider" + } + } + /** * Generate response with streaming callback support */ From 853ae3699bcbe6a53f811fb1a6c1b897df8ab44c Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 14:08:39 +0200 Subject: [PATCH 23/46] request live --- src/services/ghost/GhostModel.ts | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 5ac65abb796..bc41633c768 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -6,7 +6,6 @@ import { ApiStreamChunk } from "../../api/transform/stream" export class GhostModel { private apiHandler: ApiHandler | null = null - private providerDisplayName: string | null = null public loaded = false constructor(apiHandler: ApiHandler | null = null) { @@ -17,7 +16,6 @@ export class GhostModel { } private cleanup(): void { this.apiHandler = null - this.providerDisplayName = null this.loaded = false } @@ -61,13 +59,6 @@ export class GhostModel { if (this.apiHandler instanceof OpenRouterHandler) { await this.apiHandler.fetchModel() } - - const handler = this.apiHandler as any - if (handler.providerName && typeof handler.providerName === "string") { - this.providerDisplayName = handler.providerName - } else { - this.providerDisplayName = "Unknown Provider" - } } /** @@ -130,14 +121,20 @@ export class GhostModel { } public getModelName(): string | null { - if (!this.apiHandler) { - return null - } + if (!this.apiHandler) return null + return this.apiHandler.getModel().id ?? "unknown" } public getProviderDisplayName(): string | null { - return this.providerDisplayName + if (!this.apiHandler) return null + + const handler = this.apiHandler as any + if (handler.providerName && typeof handler.providerName === "string") { + return handler.providerName + } else { + return "unknown" + } } public hasValidCredentials(): boolean { From c0423f738963038dd8dc0884e236423e8f018e15 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 14:30:19 +0200 Subject: [PATCH 24/46] add comment and make clear why we need a separate loaded var --- src/services/ghost/GhostModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index bc41633c768..2848fe08105 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -38,7 +38,7 @@ export class GhostModel { } } - this.loaded = false + this.loaded = true // we loaded, and found nothing, but we do not wish to reload return false } From 2bdf6862a622651b978069a16c66912f23c4d968 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 14:40:11 +0200 Subject: [PATCH 25/46] remove commented out code --- src/services/ghost/GhostStatusBar.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/services/ghost/GhostStatusBar.ts b/src/services/ghost/GhostStatusBar.ts index c2fc2ca24ff..6b536749667 100644 --- a/src/services/ghost/GhostStatusBar.ts +++ b/src/services/ghost/GhostStatusBar.ts @@ -68,12 +68,6 @@ export class GhostStatusBar { if (this.enabled) this.render() } - // TODO: Bring back paused state in the future - // private renderPaused() { - // this.statusBar.text = t("kilocode:ghost.statusBar.disabled") - // this.statusBar.tooltip = t("kilocode:ghost.statusBar.tooltip.disabled") - // } - private renderTokenError() { this.statusBar.text = t("kilocode:ghost.statusBar.warning") this.statusBar.tooltip = t("kilocode:ghost.statusBar.tooltip.tokenError") From 79d6c60a7f724c79d617523d854ae896dc26195a Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 14:50:35 +0200 Subject: [PATCH 26/46] add provider and model to the settings --- packages/types/src/kilocode/kilocode.ts | 2 + src/services/ghost/GhostProvider.ts | 8 ++- .../settings/GhostServiceSettings.tsx | 28 ++++++++- .../__tests__/GhostServiceSettings.spec.tsx | 59 +++++++++++++++++++ 4 files changed, 94 insertions(+), 3 deletions(-) diff --git a/packages/types/src/kilocode/kilocode.ts b/packages/types/src/kilocode/kilocode.ts index 7da68614c77..60c478aefa7 100644 --- a/packages/types/src/kilocode/kilocode.ts +++ b/packages/types/src/kilocode/kilocode.ts @@ -7,6 +7,8 @@ export const ghostServiceSettingsSchema = z enableQuickInlineTaskKeybinding: z.boolean().optional(), enableSmartInlineTaskKeybinding: z.boolean().optional(), showGutterAnimation: z.boolean().optional(), + provider: z.string().optional(), + model: z.string().optional(), }) .optional() diff --git a/src/services/ghost/GhostProvider.ts b/src/services/ghost/GhostProvider.ts index 1941578030b..ab320a62e96 100644 --- a/src/services/ghost/GhostProvider.ts +++ b/src/services/ghost/GhostProvider.ts @@ -118,7 +118,12 @@ export class GhostProvider { if (!this.settings) { return } - await ContextProxy.instance?.setValues?.({ ghostServiceSettings: this.settings }) + const settingsWithModelInfo = { + ...this.settings, + provider: this.getCurrentProviderName(), + model: this.getCurrentModelName(), + } + await ContextProxy.instance?.setValues?.({ ghostServiceSettings: settingsWithModelInfo }) await this.cline.postStateToWebview() } @@ -128,6 +133,7 @@ export class GhostProvider { this.cursorAnimation.updateSettings(this.settings || undefined) await this.updateGlobalContext() this.updateStatusBar() + await this.saveSettings() } public async disable() { diff --git a/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx b/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx index 49cee317d84..a571a4d6e09 100644 --- a/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx +++ b/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx @@ -26,8 +26,14 @@ export const GhostServiceSettingsView = ({ ...props }: GhostServiceSettingsViewProps) => { const { t } = useAppTranslation() - const { enableAutoTrigger, autoTriggerDelay, enableQuickInlineTaskKeybinding, enableSmartInlineTaskKeybinding } = - ghostServiceSettings || {} + const { + enableAutoTrigger, + autoTriggerDelay, + enableQuickInlineTaskKeybinding, + enableSmartInlineTaskKeybinding, + provider, + model, + } = ghostServiceSettings || {} const keybindings = useKeybindings(["kilo-code.addToContextAndFocus", "kilo-code.ghost.generateSuggestions"]) const normalizedDelay = normalizeAutoTriggerDelay(autoTriggerDelay) @@ -77,6 +83,24 @@ export const GhostServiceSettingsView = ({
+
+
+ {provider && model ? ( + <> +
+ Provider: {provider} +
+
+ Model: {model} +
+ + ) : ( +
+ No suitable autocomplete model found. Please configure a provider in the API settings. +
+ )} +
+
diff --git a/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx b/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx index 4298bcd5ba1..73370ea5d04 100644 --- a/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx +++ b/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx @@ -77,6 +77,8 @@ const defaultGhostServiceSettings: GhostServiceSettings = { autoTriggerDelay: 3, enableQuickInlineTaskKeybinding: false, enableSmartInlineTaskKeybinding: false, + provider: "openrouter", + model: "openai/gpt-4o-mini", } const renderComponent = (props = {}) => { @@ -240,4 +242,61 @@ describe("GhostServiceSettingsView", () => { // We should have multiple description divs for the different settings expect(descriptionDivs.length).toBeGreaterThan(2) }) + + it("displays provider and model information when available", () => { + renderComponent({ + ghostServiceSettings: { + ...defaultGhostServiceSettings, + provider: "openrouter", + model: "openai/gpt-4o-mini", + }, + }) + + expect(screen.getByText(/Provider:/)).toBeInTheDocument() + expect(screen.getByText(/openrouter/)).toBeInTheDocument() + expect(screen.getByText(/Model:/)).toBeInTheDocument() + expect(screen.getByText(/openai\/gpt-4o-mini/)).toBeInTheDocument() + }) + + it("displays error message when provider and model are not configured", () => { + renderComponent({ + ghostServiceSettings: { + ...defaultGhostServiceSettings, + provider: undefined, + model: undefined, + }, + }) + + expect( + screen.getByText(/No suitable autocomplete model found. Please configure a provider in the API settings./), + ).toBeInTheDocument() + }) + + it("displays error message when only provider is missing", () => { + renderComponent({ + ghostServiceSettings: { + ...defaultGhostServiceSettings, + provider: undefined, + model: "openai/gpt-4o-mini", + }, + }) + + expect( + screen.getByText(/No suitable autocomplete model found. Please configure a provider in the API settings./), + ).toBeInTheDocument() + }) + + it("displays error message when only model is missing", () => { + renderComponent({ + ghostServiceSettings: { + ...defaultGhostServiceSettings, + provider: "openrouter", + model: undefined, + }, + }) + + expect( + screen.getByText(/No suitable autocomplete model found. Please configure a provider in the API settings./), + ).toBeInTheDocument() + }) }) From b3537233b95889da1996863678704cb34b127d54 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 15 Oct 2025 17:00:12 +0200 Subject: [PATCH 27/46] skip kilocode provider with 0 balance so people who use kilo for free models can use a paid autocomplete provider --- packages/types/src/provider-settings.ts | 71 ++++++++ src/services/ghost/GhostModel.ts | 14 +- .../ghost/__tests__/GhostModel.spec.ts | 166 ++++++++++++++++++ .../settings/GhostServiceSettings.tsx | 45 +++-- webview-ui/src/i18n/locales/en/kilocode.json | 1 + 5 files changed, 278 insertions(+), 19 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 0b80ad3246c..e99317ed4b2 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -169,6 +169,77 @@ export const AUTOCOMPLETE_PROVIDER_MODELS = { openrouter: "mistralai/codestral-2508", } as const satisfies Record, string> +export type AutocompleteProviderKey = keyof typeof AUTOCOMPLETE_PROVIDER_MODELS + +export type ProviderUsabilityChecker = ( + provider: AutocompleteProviderKey, + providerSettingsManager: any, +) => Promise + +export const defaultProviderUsabilityChecker: ProviderUsabilityChecker = async (provider, providerSettingsManager) => { + if (provider === "kilocode") { + // Check if kilocode balance is greater than zero + try { + const profiles = await providerSettingsManager.listConfig() + const kilocodeProfile = profiles.find((p: any) => p.apiProvider === "kilocode") + + if (!kilocodeProfile) { + return false + } + + const profile = await providerSettingsManager.getProfile({ id: kilocodeProfile.id }) + const kilocodeToken = profile.kilocodeToken + + if (!kilocodeToken) { + return false + } + + // Simple function to get base URI from token (copied from token.ts) + const getKiloBaseUriFromToken = (kilocodeToken?: string) => { + if (kilocodeToken) { + try { + const parts = kilocodeToken.split(".") + if (parts.length < 2) return "https://api.kilocode.ai" + const payload_string = parts[1] + if (!payload_string) return "https://api.kilocode.ai" + const payload_json = + typeof atob !== "undefined" + ? atob(payload_string) + : Buffer.from(payload_string, "base64").toString() + const payload = JSON.parse(payload_json) + if (payload.env === "development") return "http://localhost:3000" + } catch (_error) { + console.warn("Failed to get base URL from Kilo Code token") + } + } + return "https://api.kilocode.ai" + } + + const baseUrl = getKiloBaseUriFromToken(kilocodeToken) + + const response = await fetch(`${baseUrl}/api/profile/balance`, { + headers: { + Authorization: `Bearer ${kilocodeToken}`, + }, + }) + + if (!response.ok) { + return false + } + + const data = await response.json() + const balance = data.data?.balance ?? 0 + return balance > 0 + } catch (error) { + console.error("Error checking kilocode balance:", error) + return false + } + } + + // For all other providers, assume they are usable + return true +} + /** * ProviderSettingsEntry */ diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index 2848fe08105..aee1c100885 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -1,4 +1,9 @@ -import { AUTOCOMPLETE_PROVIDER_MODELS, modelIdKeysByProvider, ProviderSettingsEntry } from "@roo-code/types" +import { + AUTOCOMPLETE_PROVIDER_MODELS, + defaultProviderUsabilityChecker, + modelIdKeysByProvider, + ProviderSettingsEntry, +} from "@roo-code/types" import { ApiHandler, buildApiHandler } from "../../api" import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager" import { OpenRouterHandler } from "../../api/providers" @@ -27,11 +32,18 @@ export class GhostModel { this.cleanup() + // Check providers in order, but skip unusable ones (e.g., kilocode with zero balance) for (const provider of supportedProviders) { const selectedProfile = profiles.find( (x): x is typeof x & { apiProvider: string } => x?.apiProvider === provider, ) if (selectedProfile) { + // Check if the provider is usable (e.g., kilocode balance > 0) + const isUsable = await defaultProviderUsabilityChecker(provider, providerSettingsManager) + if (!isUsable) { + continue // Skip to next provider if not usable + } + this.loadProfile(providerSettingsManager, selectedProfile, provider) this.loaded = true return true diff --git a/src/services/ghost/__tests__/GhostModel.spec.ts b/src/services/ghost/__tests__/GhostModel.spec.ts index 95d6505fc1c..4a86c93bd17 100644 --- a/src/services/ghost/__tests__/GhostModel.spec.ts +++ b/src/services/ghost/__tests__/GhostModel.spec.ts @@ -109,6 +109,172 @@ describe("GhostModel", () => { }) }) + describe("provider usability", () => { + beforeEach(() => { + // Mock fetch globally for these tests + vi.stubGlobal("fetch", vi.fn()) + }) + + afterEach(() => { + // Restore fetch + vi.unstubAllGlobals() + }) + + it("should skip kilocode provider when balance is zero and use openrouter instead", async () => { + const profiles = [ + { id: "1", name: "kilocode-profile", apiProvider: "kilocode" }, + { id: "2", name: "openrouter-profile", apiProvider: "openrouter" }, + ] as any + + vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles) + + // Mock profiles with tokens + vi.mocked(mockProviderSettingsManager.getProfile).mockImplementation(async (args: any) => { + if (args.id === "1") { + return { + id: "1", + name: "kilocode-profile", + apiProvider: "kilocode", + kilocodeToken: "test-token", + } as any + } else if (args.id === "2") { + return { + id: "2", + name: "openrouter-profile", + apiProvider: "openrouter", + openRouterApiKey: "test-key", + } as any + } + return null as any + }) + + // Mock fetch to return zero balance for kilocode + ;(global.fetch as any).mockImplementation(async (url: string) => { + if (url.includes("/api/profile/balance")) { + return { + ok: true, + json: async () => ({ data: { balance: 0 } }), + } as any + } + // For other URLs, return a basic response + return { + ok: true, + json: async () => ({}), + } as any + }) + + const model = new GhostModel() + const result = await model.reload(mockProviderSettingsManager) + + // Should have tried both providers but used openrouter (since kilocode balance is 0) + expect(result).toBe(true) + expect(model.loaded).toBe(true) + }) + + it("should use kilocode provider when balance is greater than zero", async () => { + const profiles = [ + { id: "1", name: "kilocode-profile", apiProvider: "kilocode" }, + { id: "2", name: "openrouter-profile", apiProvider: "openrouter" }, + ] as any + + vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles) + + // Mock profiles with tokens + vi.mocked(mockProviderSettingsManager.getProfile).mockImplementation(async (args: any) => { + if (args.id === "1") { + return { + id: "1", + name: "kilocode-profile", + apiProvider: "kilocode", + kilocodeToken: "test-token", + } as any + } else if (args.id === "2") { + return { + id: "2", + name: "openrouter-profile", + apiProvider: "openrouter", + openRouterApiKey: "test-key", + } as any + } + return null as any + }) + + // Mock fetch to return positive balance for kilocode + ;(global.fetch as any).mockImplementation(async (url: string) => { + if (url.includes("/api/profile/balance")) { + return { + ok: true, + json: async () => ({ data: { balance: 10.5 } }), + } as any + } + // For other URLs, return a basic response + return { + ok: true, + json: async () => ({}), + } as any + }) + + const model = new GhostModel() + const result = await model.reload(mockProviderSettingsManager) + + // Should have used kilocode provider (first one with positive balance) + expect(result).toBe(true) + expect(model.loaded).toBe(true) + }) + + it("should handle kilocode provider with no token", async () => { + const profiles = [ + { id: "1", name: "kilocode-profile", apiProvider: "kilocode" }, + { id: "2", name: "openrouter-profile", apiProvider: "openrouter" }, + ] as any + + vi.mocked(mockProviderSettingsManager.listConfig).mockResolvedValue(profiles) + + // Mock profiles - kilocode without token + vi.mocked(mockProviderSettingsManager.getProfile).mockImplementation(async (args: any) => { + if (args.id === "1") { + return { + id: "1", + name: "kilocode-profile", + apiProvider: "kilocode", + kilocodeToken: "", // No token + } as any + } else if (args.id === "2") { + return { + id: "2", + name: "openrouter-profile", + apiProvider: "openrouter", + openRouterApiKey: "test-key", + } as any + } + return null as any + }) + + // Mock fetch to handle the no-token case + ;(global.fetch as any).mockImplementation(async (url: string) => { + if (url.includes("/api/profile/balance")) { + // This should not be called since there's no token + return { + ok: false, + status: 401, + } as any + } + // For other URLs, return a basic response + return { + ok: true, + json: async () => ({}), + } as any + }) + + const model = new GhostModel() + const result = await model.reload(mockProviderSettingsManager) + + // Should skip kilocode (no token) and use openrouter + expect(result).toBe(true) + expect(model.loaded).toBe(true) + }) + }) + describe("getProviderDisplayName", () => { it("returns null when no provider is loaded", () => { const model = new GhostModel() diff --git a/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx b/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx index a571a4d6e09..998124e84cf 100644 --- a/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx +++ b/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx @@ -83,24 +83,6 @@ export const GhostServiceSettingsView = ({
-
-
- {provider && model ? ( - <> -
- Provider: {provider} -
-
- Model: {model} -
- - ) : ( -
- No suitable autocomplete model found. Please configure a provider in the API settings. -
- )} -
-
@@ -192,6 +174,33 @@ export const GhostServiceSettingsView = ({ />
+ +
+
+ +
{t("kilocode:ghost.settings.model")}
+
+
+ +
+
+ {provider && model ? ( + <> +
+ Provider: {provider} +
+
+ Model: {model} +
+ + ) : ( +
+ No suitable autocomplete model found. Please configure a provider in the API + settings. +
+ )} +
+
diff --git a/webview-ui/src/i18n/locales/en/kilocode.json b/webview-ui/src/i18n/locales/en/kilocode.json index 6ccc09a6c9b..d99b31530ad 100644 --- a/webview-ui/src/i18n/locales/en/kilocode.json +++ b/webview-ui/src/i18n/locales/en/kilocode.json @@ -219,6 +219,7 @@ "title": "Autocomplete", "settings": { "triggers": "Triggers", + "model": "Model", "enableAutoTrigger": { "label": "Pause to Complete", "description": "When enabled, Kilo Code will automatically trigger Autocomplete when you pause typing. This can be useful for quick fixes and suggestions." From f95f8babaa6f60b2f3c4740391df1754986d446a Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 09:29:54 +0200 Subject: [PATCH 28/46] fix --- packages/types/src/provider-settings.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index e99317ed4b2..4efe01d6b59 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -171,9 +171,14 @@ export const AUTOCOMPLETE_PROVIDER_MODELS = { export type AutocompleteProviderKey = keyof typeof AUTOCOMPLETE_PROVIDER_MODELS +interface ProviderSettingsManager { + listConfig(): Promise + getProfile(params: { id: string }): Promise +} + export type ProviderUsabilityChecker = ( provider: AutocompleteProviderKey, - providerSettingsManager: any, + providerSettingsManager: ProviderSettingsManager, ) => Promise export const defaultProviderUsabilityChecker: ProviderUsabilityChecker = async (provider, providerSettingsManager) => { @@ -181,7 +186,7 @@ export const defaultProviderUsabilityChecker: ProviderUsabilityChecker = async ( // Check if kilocode balance is greater than zero try { const profiles = await providerSettingsManager.listConfig() - const kilocodeProfile = profiles.find((p: any) => p.apiProvider === "kilocode") + const kilocodeProfile = profiles.find((p) => p.apiProvider === "kilocode") if (!kilocodeProfile) { return false From f78f4d459b037f0c895d3f454556c7e22491460c Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 17:26:58 +0200 Subject: [PATCH 29/46] reuse balanche checking --- packages/types/src/kilocode-utils.ts | 56 +++++++++++++++++++++++++ packages/types/src/provider-settings.ts | 39 +---------------- 2 files changed, 58 insertions(+), 37 deletions(-) create mode 100644 packages/types/src/kilocode-utils.ts diff --git a/packages/types/src/kilocode-utils.ts b/packages/types/src/kilocode-utils.ts new file mode 100644 index 00000000000..e84148d2219 --- /dev/null +++ b/packages/types/src/kilocode-utils.ts @@ -0,0 +1,56 @@ +/** + * Kilocode utility functions shared across the codebase + */ + +/** + * Get the base URI for Kilocode API from the JWT token + * @param kilocodeToken - The Kilocode JWT token + * @returns The base URI for the Kilocode API + */ +export function getKiloBaseUriFromToken(kilocodeToken?: string): string { + if (kilocodeToken) { + try { + const parts = kilocodeToken.split(".") + if (parts.length < 2) return "https://api.kilocode.ai" + const payload_string = parts[1] + if (!payload_string) return "https://api.kilocode.ai" + const payload_json = + typeof atob !== "undefined" ? atob(payload_string) : Buffer.from(payload_string, "base64").toString() + const payload = JSON.parse(payload_json) + // Note: this is UNTRUSTED, so we need to make sure we're OK with this being manipulated by an attacker + // e.g. we should not read URIs from the JWT directly + if (payload.env === "development") return "http://localhost:3000" + } catch (_error) { + console.warn("Failed to get base URL from Kilo Code token") + } + } + return "https://api.kilocode.ai" +} + +/** + * Check if the Kilocode account has a positive balance + * @param kilocodeToken - The Kilocode JWT token + * @returns Promise - True if balance > 0, false otherwise + */ +export async function checkKilocodeBalance(kilocodeToken: string): Promise { + try { + const baseUrl = getKiloBaseUriFromToken(kilocodeToken) + + const response = await fetch(`${baseUrl}/api/profile/balance`, { + headers: { + Authorization: `Bearer ${kilocodeToken}`, + }, + }) + + if (!response.ok) { + return false + } + + const data = await response.json() + const balance = data.data?.balance ?? 0 + return balance > 0 + } catch (error) { + console.error("Error checking kilocode balance:", error) + return false + } +} diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 4efe01d6b59..49131bfaa22 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -1,6 +1,7 @@ import { z } from "zod" import { modelInfoSchema, reasoningEffortWithMinimalSchema, verbosityLevelsSchema, serviceTierSchema } from "./model.js" +import { checkKilocodeBalance } from "./kilocode-utils.js" import { codebaseIndexProviderSchema } from "./codebase-index.js" import { anthropicModels, @@ -183,7 +184,6 @@ export type ProviderUsabilityChecker = ( export const defaultProviderUsabilityChecker: ProviderUsabilityChecker = async (provider, providerSettingsManager) => { if (provider === "kilocode") { - // Check if kilocode balance is greater than zero try { const profiles = await providerSettingsManager.listConfig() const kilocodeProfile = profiles.find((p) => p.apiProvider === "kilocode") @@ -199,42 +199,7 @@ export const defaultProviderUsabilityChecker: ProviderUsabilityChecker = async ( return false } - // Simple function to get base URI from token (copied from token.ts) - const getKiloBaseUriFromToken = (kilocodeToken?: string) => { - if (kilocodeToken) { - try { - const parts = kilocodeToken.split(".") - if (parts.length < 2) return "https://api.kilocode.ai" - const payload_string = parts[1] - if (!payload_string) return "https://api.kilocode.ai" - const payload_json = - typeof atob !== "undefined" - ? atob(payload_string) - : Buffer.from(payload_string, "base64").toString() - const payload = JSON.parse(payload_json) - if (payload.env === "development") return "http://localhost:3000" - } catch (_error) { - console.warn("Failed to get base URL from Kilo Code token") - } - } - return "https://api.kilocode.ai" - } - - const baseUrl = getKiloBaseUriFromToken(kilocodeToken) - - const response = await fetch(`${baseUrl}/api/profile/balance`, { - headers: { - Authorization: `Bearer ${kilocodeToken}`, - }, - }) - - if (!response.ok) { - return false - } - - const data = await response.json() - const balance = data.data?.balance ?? 0 - return balance > 0 + return await checkKilocodeBalance(kilocodeToken) } catch (error) { console.error("Error checking kilocode balance:", error) return false From 47f5b423facc48114ce97e2dc41287ea9798dff0 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 14:53:43 +0200 Subject: [PATCH 30/46] remove translation --- webview-ui/src/i18n/locales/ar/settings.json | 1 - webview-ui/src/i18n/locales/ca/settings.json | 1 - webview-ui/src/i18n/locales/cs/settings.json | 1 - webview-ui/src/i18n/locales/de/settings.json | 1 - webview-ui/src/i18n/locales/en/settings.json | 1 - webview-ui/src/i18n/locales/es/settings.json | 1 - webview-ui/src/i18n/locales/fr/settings.json | 1 - webview-ui/src/i18n/locales/hi/settings.json | 1 - webview-ui/src/i18n/locales/id/settings.json | 1 - webview-ui/src/i18n/locales/it/settings.json | 1 - webview-ui/src/i18n/locales/ja/settings.json | 1 - webview-ui/src/i18n/locales/ko/settings.json | 1 - webview-ui/src/i18n/locales/nl/settings.json | 1 - webview-ui/src/i18n/locales/pl/settings.json | 1 - webview-ui/src/i18n/locales/pt-BR/settings.json | 1 - webview-ui/src/i18n/locales/ru/settings.json | 1 - webview-ui/src/i18n/locales/th/settings.json | 1 - webview-ui/src/i18n/locales/tr/settings.json | 1 - webview-ui/src/i18n/locales/uk/settings.json | 1 - webview-ui/src/i18n/locales/vi/settings.json | 1 - webview-ui/src/i18n/locales/zh-CN/settings.json | 1 - webview-ui/src/i18n/locales/zh-TW/settings.json | 1 - 22 files changed, 22 deletions(-) diff --git a/webview-ui/src/i18n/locales/ar/settings.json b/webview-ui/src/i18n/locales/ar/settings.json index 480f1c75ce8..960b92fac54 100644 --- a/webview-ui/src/i18n/locales/ar/settings.json +++ b/webview-ui/src/i18n/locales/ar/settings.json @@ -239,7 +239,6 @@ "description": "احفظ إعدادات متعددة للتبديل السريع بين مزوّدين ونماذج.", "apiProvider": "مزود API", "model": "النموذج", - "autocompleteNotSupported": "⚠️ الإكمال التلقائي مدعوم فقط مع المزودين التاليين: {{providers}}", "nameEmpty": "الاسم ما يصير فاضي", "nameExists": "الاسم مستخدم مسبقًا", "deleteProfile": "حذف الملف", diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 6880e2f7ef4..179c62668b8 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -230,7 +230,6 @@ "description": "Deseu diferents configuracions d'API per canviar ràpidament entre proveïdors i configuracions.", "apiProvider": "Proveïdor d'API", "model": "Model", - "autocompleteNotSupported": "⚠️ L'autocompletat només és compatible amb els següents proveïdors: {{providers}}", "nameEmpty": "El nom no pot estar buit", "nameExists": "Ja existeix un perfil amb aquest nom", "deleteProfile": "Esborrar perfil", diff --git a/webview-ui/src/i18n/locales/cs/settings.json b/webview-ui/src/i18n/locales/cs/settings.json index 3cd6d534c36..03621a6ac66 100644 --- a/webview-ui/src/i18n/locales/cs/settings.json +++ b/webview-ui/src/i18n/locales/cs/settings.json @@ -239,7 +239,6 @@ "description": "Uložte si různé konfigurace API pro rychlé přepínání mezi poskytovateli a nastaveními.", "apiProvider": "Poskytovatel API", "model": "Model", - "autocompleteNotSupported": "⚠️ Automatické dokončování je podporováno pouze s následujícími poskytovateli: {{providers}}", "nameEmpty": "Jméno nemůže být prázdné", "nameExists": "Profil s tímto názvem již existuje", "deleteProfile": "Smazat profil", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index fc172ae9c3d..37acc150c78 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -230,7 +230,6 @@ "description": "Speicher verschiedene API-Konfigurationen, um schnell zwischen Anbietern und Einstellungen zu wechseln.", "apiProvider": "API-Anbieter", "model": "Modell", - "autocompleteNotSupported": "⚠️ Autovervollständigung wird nur mit folgenden Anbietern unterstützt: {{providers}}", "nameEmpty": "Name darf nicht leer sein", "nameExists": "Ein Profil mit diesem Namen existiert bereits", "deleteProfile": "Profil löschen", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index b6e71d5ac2c..31dbb4b2a8f 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -235,7 +235,6 @@ "description": "Save different API configurations to quickly switch between providers and settings.", "apiProvider": "API Provider", "model": "Model", - "autocompleteNotSupported": "⚠️ Autocomplete is only supported with the following providers: {{providers}}", "nameEmpty": "Name cannot be empty", "nameExists": "A profile with this name already exists", "deleteProfile": "Delete Profile", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index b616aa44eaf..bce2c528a15 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -230,7 +230,6 @@ "description": "Guarde diferentes configuraciones de API para cambiar rápidamente entre proveedores y ajustes.", "apiProvider": "Proveedor de API", "model": "Modelo", - "autocompleteNotSupported": "⚠️ El autocompletado solo es compatible con los siguientes proveedores: {{providers}}", "nameEmpty": "El nombre no puede estar vacío", "nameExists": "Ya existe un perfil con este nombre", "deleteProfile": "Eliminar perfil", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 13935e0973a..8e754e7381e 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -230,7 +230,6 @@ "description": "Enregistrez différentes configurations d'API pour basculer rapidement entre les fournisseurs et les paramètres.", "apiProvider": "Fournisseur d'API", "model": "Modèle", - "autocompleteNotSupported": "⚠️ L'autocomplétion n'est prise en charge qu'avec les fournisseurs suivants : {{providers}}", "nameEmpty": "Le nom ne peut pas être vide", "nameExists": "Un profil avec ce nom existe déjà", "deleteProfile": "Supprimer le profil", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 0e8b117c1f5..2c7f7fcbdf1 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -230,7 +230,6 @@ "description": "विभिन्न API कॉन्फ़िगरेशन सहेजें ताकि प्रदाताओं और सेटिंग्स के बीच त्वरित रूप से स्विच कर सकें।", "apiProvider": "API प्रदाता", "model": "मॉडल", - "autocompleteNotSupported": "⚠️ स्वतः पूर्ण केवल निम्नलिखित प्रदाताओं के साथ समर्थित है: {{providers}}", "nameEmpty": "नाम खाली नहीं हो सकता", "nameExists": "इस नाम वाला प्रोफ़ाइल पहले से मौजूद है", "deleteProfile": "प्रोफ़ाइल हटाएं", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 6a9e9392f8c..d72cfd58e58 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -230,7 +230,6 @@ "description": "Simpan konfigurasi API yang berbeda untuk beralih dengan cepat antara provider dan pengaturan.", "apiProvider": "Provider API", "model": "Model", - "autocompleteNotSupported": "⚠️ Pelengkapan otomatis hanya didukung dengan penyedia berikut: {{providers}}", "nameEmpty": "Nama tidak boleh kosong", "nameExists": "Profil dengan nama ini sudah ada", "deleteProfile": "Hapus Profil", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 3c4e07be43a..737f200f6a5 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -231,7 +231,6 @@ "description": "Salva diverse configurazioni API per passare rapidamente tra fornitori e impostazioni.", "apiProvider": "Fornitore API", "model": "Modello", - "autocompleteNotSupported": "⚠️ Il completamento automatico è supportato solo con i seguenti provider: {{providers}}", "nameEmpty": "Il nome non può essere vuoto", "nameExists": "Esiste già un profilo con questo nome", "deleteProfile": "Elimina profilo", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index cac97304d9f..882183a413d 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -231,7 +231,6 @@ "description": "異なるAPI設定を保存して、プロバイダーと設定をすばやく切り替えることができます。", "apiProvider": "APIプロバイダー", "model": "モデル", - "autocompleteNotSupported": "⚠️ オートコンプリートは次のプロバイダーでのみサポートされています: {{providers}}", "nameEmpty": "名前を空にすることはできません", "nameExists": "この名前のプロファイルは既に存在します", "deleteProfile": "プロファイルを削除", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index a0a0894b569..b02bdad689b 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -230,7 +230,6 @@ "description": "다양한 API 구성을 저장하여 제공자와 설정 간에 빠르게 전환할 수 있습니다.", "apiProvider": "API 제공자", "model": "모델", - "autocompleteNotSupported": "⚠️ 자동 완성은 다음 제공자에서만 지원됩니다: {{providers}}", "nameEmpty": "이름은 비워둘 수 없습니다", "nameExists": "이 이름의 프로필이 이미 존재합니다", "deleteProfile": "프로필 삭제", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 20629016dbc..a40ee3100e6 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -230,7 +230,6 @@ "description": "Sla verschillende API-configuraties op om snel te wisselen tussen providers en instellingen.", "apiProvider": "API-provider", "model": "Model", - "autocompleteNotSupported": "⚠️ Automatisch aanvullen wordt alleen ondersteund met de volgende providers: {{providers}}", "nameEmpty": "Naam mag niet leeg zijn", "nameExists": "Er bestaat al een profiel met deze naam", "deleteProfile": "Profiel verwijderen", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index cbf50aab1e6..c0a19078212 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -230,7 +230,6 @@ "description": "Zapisz różne konfiguracje API, aby szybko przełączać się między dostawcami i ustawieniami.", "apiProvider": "Dostawca API", "model": "Model", - "autocompleteNotSupported": "⚠️ Autouzupełnianie jest obsługiwane tylko z następującymi dostawcami: {{providers}}", "nameEmpty": "Nazwa nie może być pusta", "nameExists": "Profil o tej nazwie już istnieje", "deleteProfile": "Usuń profil", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 79ae74d0c60..7f4624e8630 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -230,7 +230,6 @@ "description": "Salve diferentes configurações de API para alternar rapidamente entre provedores e configurações.", "apiProvider": "Provedor de API", "model": "Modelo", - "autocompleteNotSupported": "⚠️ O preenchimento automático só é compatível com os seguintes provedores: {{providers}}", "nameEmpty": "O nome não pode estar vazio", "nameExists": "Já existe um perfil com este nome", "deleteProfile": "Excluir perfil", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 99b22f43ffd..e4bd8876987 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -230,7 +230,6 @@ "description": "Сохраняйте различные конфигурации API для быстрого переключения между провайдерами и настройками.", "apiProvider": "Провайдер API", "model": "Модель", - "autocompleteNotSupported": "⚠️ Автозаполнение поддерживается только следующими провайдерами: {{providers}}", "nameEmpty": "Имя не может быть пустым", "nameExists": "Профиль с таким именем уже существует", "deleteProfile": "Удалить профиль", diff --git a/webview-ui/src/i18n/locales/th/settings.json b/webview-ui/src/i18n/locales/th/settings.json index 2d19b8264b1..606dc766c1f 100644 --- a/webview-ui/src/i18n/locales/th/settings.json +++ b/webview-ui/src/i18n/locales/th/settings.json @@ -236,7 +236,6 @@ "description": "บันทึกการกำหนดค่า API ต่างๆ เพื่อสลับระหว่างผู้ให้บริการและการตั้งค่าต่างๆ ได้อย่างรวดเร็ว", "apiProvider": "ผู้ให้บริการ API", "model": "โมเดล", - "autocompleteNotSupported": "⚠️ การเติมข้อความอัตโนมัติรองรับเฉพาะผู้ให้บริการต่อไปนี้: {{providers}}", "nameEmpty": "ชื่อต้องไม่ว่างเปล่า", "nameExists": "มีโปรไฟล์ชื่อนี้อยู่แล้ว", "deleteProfile": "ลบโปรไฟล์", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index bb4e9c147b8..8f9bb5ba70d 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -231,7 +231,6 @@ "description": "Sağlayıcılar ve ayarlar arasında hızlıca geçiş yapmak için farklı API yapılandırmalarını kaydedin.", "apiProvider": "API Sağlayıcı", "model": "Model", - "autocompleteNotSupported": "⚠️ Otomatik tamamlama yalnızca şu sağlayıcılarla desteklenir: {{providers}}", "nameEmpty": "İsim boş olamaz", "nameExists": "Bu isme sahip bir profil zaten mevcut", "deleteProfile": "Profili sil", diff --git a/webview-ui/src/i18n/locales/uk/settings.json b/webview-ui/src/i18n/locales/uk/settings.json index 378deb7c942..0f4a3ce1b7a 100644 --- a/webview-ui/src/i18n/locales/uk/settings.json +++ b/webview-ui/src/i18n/locales/uk/settings.json @@ -242,7 +242,6 @@ "description": "Збережіть різні конфігурації API для швидкого перемикання між провайдерами та налаштуваннями.", "apiProvider": "Провайдер API", "model": "Модель", - "autocompleteNotSupported": "⚠️ Автодоповнення підтримується лише з наступними провайдерами: {{providers}}", "nameEmpty": "Ім'я не може бути порожнім", "nameExists": "Профіль із таким іменем уже існує", "deleteProfile": "Видалити профіль", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 950afdc28f7..de37986f5be 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -230,7 +230,6 @@ "description": "Lưu các cấu hình API khác nhau để nhanh chóng chuyển đổi giữa các nhà cung cấp và cài đặt.", "apiProvider": "Nhà cung cấp API", "model": "Mẫu", - "autocompleteNotSupported": "⚠️ Tự động hoàn thành chỉ được hỗ trợ với các nhà cung cấp sau: {{providers}}", "nameEmpty": "Tên không được để trống", "nameExists": "Đã tồn tại một hồ sơ với tên này", "deleteProfile": "Xóa hồ sơ", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index b881cc3b437..0f200d24bd2 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -231,7 +231,6 @@ "description": "保存多组API配置便于快速切换", "apiProvider": "API提供商", "model": "模型", - "autocompleteNotSupported": "⚠️ 自动完成仅支持以下提供商: {{providers}}", "nameEmpty": "名称不能为空", "nameExists": "已存在同名的配置文件", "deleteProfile": "删除配置文件", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 90c07461c39..8b065ee52cb 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -231,7 +231,6 @@ "description": "儲存不同的 API 設定以快速切換供應商和設定。", "apiProvider": "API 供應商", "model": "模型", - "autocompleteNotSupported": "⚠️ 自動完成僅支援以下提供者: {{providers}}", "nameEmpty": "名稱不能為空", "nameExists": "已存在同名的設定檔", "deleteProfile": "刪除設定檔", From 83aa99ac2e83d75edde45c7f595c6663a4b9d681 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 14:55:13 +0200 Subject: [PATCH 31/46] remove changes from settings screen --- .../settings/__tests__/ApiOptions.spec.tsx | 51 ------------------- .../settings/__tests__/SettingsView.spec.tsx | 9 ---- 2 files changed, 60 deletions(-) diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx index 27450ab65b5..c82518864a4 100644 --- a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx @@ -9,15 +9,6 @@ import { ExtensionStateContextProvider } from "@src/context/ExtensionStateContex import ApiOptions, { ApiOptionsProps } from "../ApiOptions" -// Mock @roo-code/types constants -vi.mock("@roo-code/types", async () => { - const actual = await vi.importActual("@roo-code/types") - return { - ...actual, - SUPPORTED_AUTOCOMPLETE_PROVIDERS: ["mistral", "kilocode", "openrouter"], - } -}) - // Mock VSCode components vi.mock("@vscode/webview-ui-toolkit/react", () => ({ VSCodeTextField: ({ children, value, onBlur }: any) => ( @@ -582,46 +573,4 @@ describe("ApiOptions", () => { expect(screen.queryByTestId("litellm-provider")).not.toBeInTheDocument() }) }) - - describe("Autocomplete provider warning", () => { - it("shows warning for unsupported providers", () => { - renderApiOptions({ - apiConfiguration: { - apiProvider: "anthropic", - }, - }) - - expect(screen.getByText("settings:providers.autocompleteNotSupported")).toBeInTheDocument() - }) - - it("does not show warning for supported providers - mistral", () => { - renderApiOptions({ - apiConfiguration: { - apiProvider: "mistral", - }, - }) - - expect(screen.queryByText("settings:providers.autocompleteNotSupported")).not.toBeInTheDocument() - }) - - it("does not show warning for supported providers - kilocode", () => { - renderApiOptions({ - apiConfiguration: { - apiProvider: "kilocode", - }, - }) - - expect(screen.queryByText("settings:providers.autocompleteNotSupported")).not.toBeInTheDocument() - }) - - it("does not show warning for supported providers - openrouter", () => { - renderApiOptions({ - apiConfiguration: { - apiProvider: "openrouter", - }, - }) - - expect(screen.queryByText("settings:providers.autocompleteNotSupported")).not.toBeInTheDocument() - }) - }) }) diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx index a3440de8733..5a9ac4d1e0c 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx @@ -8,15 +8,6 @@ import SettingsView from "../SettingsView" vi.mock("@src/utils/vscode", () => ({ vscode: { postMessage: vi.fn() } })) -// Mock @roo-code/types constants -vi.mock("@roo-code/types", async () => { - const actual = await vi.importActual("@roo-code/types") - return { - ...actual, - SUPPORTED_AUTOCOMPLETE_PROVIDERS: ["mistral", "kilocode", "openrouter"], - } -}) - // kilocode_change start // Mock the validate functions to prevent validation errors vi.mock("@src/utils/validate", () => ({ From 7e08560f6c41067218c51c8b89f9134f4b959f4f Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 17:27:20 +0200 Subject: [PATCH 32/46] also merge files here --- packages/types/src/kilocode-utils.ts | 56 ------------------------- packages/types/src/kilocode/kilocode.ts | 28 +++++++++++++ 2 files changed, 28 insertions(+), 56 deletions(-) delete mode 100644 packages/types/src/kilocode-utils.ts diff --git a/packages/types/src/kilocode-utils.ts b/packages/types/src/kilocode-utils.ts deleted file mode 100644 index e84148d2219..00000000000 --- a/packages/types/src/kilocode-utils.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Kilocode utility functions shared across the codebase - */ - -/** - * Get the base URI for Kilocode API from the JWT token - * @param kilocodeToken - The Kilocode JWT token - * @returns The base URI for the Kilocode API - */ -export function getKiloBaseUriFromToken(kilocodeToken?: string): string { - if (kilocodeToken) { - try { - const parts = kilocodeToken.split(".") - if (parts.length < 2) return "https://api.kilocode.ai" - const payload_string = parts[1] - if (!payload_string) return "https://api.kilocode.ai" - const payload_json = - typeof atob !== "undefined" ? atob(payload_string) : Buffer.from(payload_string, "base64").toString() - const payload = JSON.parse(payload_json) - // Note: this is UNTRUSTED, so we need to make sure we're OK with this being manipulated by an attacker - // e.g. we should not read URIs from the JWT directly - if (payload.env === "development") return "http://localhost:3000" - } catch (_error) { - console.warn("Failed to get base URL from Kilo Code token") - } - } - return "https://api.kilocode.ai" -} - -/** - * Check if the Kilocode account has a positive balance - * @param kilocodeToken - The Kilocode JWT token - * @returns Promise - True if balance > 0, false otherwise - */ -export async function checkKilocodeBalance(kilocodeToken: string): Promise { - try { - const baseUrl = getKiloBaseUriFromToken(kilocodeToken) - - const response = await fetch(`${baseUrl}/api/profile/balance`, { - headers: { - Authorization: `Bearer ${kilocodeToken}`, - }, - }) - - if (!response.ok) { - return false - } - - const data = await response.json() - const balance = data.data?.balance ?? 0 - return balance > 0 - } catch (error) { - console.error("Error checking kilocode balance:", error) - return false - } -} diff --git a/packages/types/src/kilocode/kilocode.ts b/packages/types/src/kilocode/kilocode.ts index 60c478aefa7..b9b09c930a5 100644 --- a/packages/types/src/kilocode/kilocode.ts +++ b/packages/types/src/kilocode/kilocode.ts @@ -54,3 +54,31 @@ export function getKiloBaseUriFromToken(kilocodeToken?: string) { } return "https://api.kilocode.ai" } + +/** + * Check if the Kilocode account has a positive balance + * @param kilocodeToken - The Kilocode JWT token + * @returns Promise - True if balance > 0, false otherwise + */ +export async function checkKilocodeBalance(kilocodeToken: string): Promise { + try { + const baseUrl = getKiloBaseUriFromToken(kilocodeToken) + + const response = await fetch(`${baseUrl}/api/profile/balance`, { + headers: { + Authorization: `Bearer ${kilocodeToken}`, + }, + }) + + if (!response.ok) { + return false + } + + const data = await response.json() + const balance = data.data?.balance ?? 0 + return balance > 0 + } catch (error) { + console.error("Error checking kilocode balance:", error) + return false + } +} From d45e447e95c67ca2f97db9a056e8a8cbb73d4d0b Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 15:55:35 +0200 Subject: [PATCH 33/46] fix import --- packages/types/src/provider-settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 49131bfaa22..3a88dfffb4a 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -1,7 +1,7 @@ import { z } from "zod" import { modelInfoSchema, reasoningEffortWithMinimalSchema, verbosityLevelsSchema, serviceTierSchema } from "./model.js" -import { checkKilocodeBalance } from "./kilocode-utils.js" +import { checkKilocodeBalance } from "./kilocode.js" import { codebaseIndexProviderSchema } from "./codebase-index.js" import { anthropicModels, From e68ea2590c3a805eb4e3c0c71fb4b25933c14b6d Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 16:17:26 +0200 Subject: [PATCH 34/46] add mocks --- .../ghost/__tests__/GhostModel.spec.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/services/ghost/__tests__/GhostModel.spec.ts b/src/services/ghost/__tests__/GhostModel.spec.ts index 4a86c93bd17..8e8dfee7bf1 100644 --- a/src/services/ghost/__tests__/GhostModel.spec.ts +++ b/src/services/ghost/__tests__/GhostModel.spec.ts @@ -156,6 +156,13 @@ describe("GhostModel", () => { json: async () => ({ data: { balance: 0 } }), } as any } + // For OpenRouter models endpoint + if (url.includes("/models")) { + return { + ok: true, + json: async () => ({ data: [] }), + } as any + } // For other URLs, return a basic response return { ok: true, @@ -207,6 +214,13 @@ describe("GhostModel", () => { json: async () => ({ data: { balance: 10.5 } }), } as any } + // For OpenRouter models endpoint + if (url.includes("/models")) { + return { + ok: true, + json: async () => ({ data: [] }), + } as any + } // For other URLs, return a basic response return { ok: true, @@ -259,6 +273,13 @@ describe("GhostModel", () => { status: 401, } as any } + // For OpenRouter models endpoint + if (url.includes("/models")) { + return { + ok: true, + json: async () => ({ data: [] }), + } as any + } // For other URLs, return a basic response return { ok: true, From 68916918fc1733fe6be53bb255fd4abf6b04f134 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 16:22:18 +0200 Subject: [PATCH 35/46] remove redundant comments --- src/services/ghost/GhostModel.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/services/ghost/GhostModel.ts b/src/services/ghost/GhostModel.ts index aee1c100885..a69f82e668d 100644 --- a/src/services/ghost/GhostModel.ts +++ b/src/services/ghost/GhostModel.ts @@ -38,11 +38,8 @@ export class GhostModel { (x): x is typeof x & { apiProvider: string } => x?.apiProvider === provider, ) if (selectedProfile) { - // Check if the provider is usable (e.g., kilocode balance > 0) const isUsable = await defaultProviderUsabilityChecker(provider, providerSettingsManager) - if (!isUsable) { - continue // Skip to next provider if not usable - } + if (!isUsable) continue this.loadProfile(providerSettingsManager, selectedProfile, provider) this.loaded = true From 3c50e8f497e22c8486b70ee43cf171854796abf1 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 16:25:43 +0200 Subject: [PATCH 36/46] remove --- webview-ui/src/components/settings/ApiOptions.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 0d0f8f3701b..91565def48b 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -8,7 +8,6 @@ import { type ProviderName, type ProviderSettings, DEFAULT_CONSECUTIVE_MISTAKE_LIMIT, - AUTOCOMPLETE_PROVIDER_MODELS, openRouterDefaultModelId, requestyDefaultModelId, glamaDefaultModelId, @@ -496,17 +495,6 @@ const ApiOptions = ({ />
- {!Object.keys(AUTOCOMPLETE_PROVIDER_MODELS).includes(selectedProvider) && ( -
- -
- {t("settings:providers.autocompleteNotSupported", { - providers: Object.keys(AUTOCOMPLETE_PROVIDER_MODELS).join(", "), - })} -
-
- )} - {errorMessage && } {/* kilocode_change start */} From 17a2e12aa80568889607bbfb57f3276e4e178fcd Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 17:14:40 +0200 Subject: [PATCH 37/46] update translations --- .../kilocode/settings/GhostServiceSettings.tsx | 3 +-- .../settings/__tests__/GhostServiceSettings.spec.tsx | 12 +++--------- webview-ui/src/i18n/locales/ar/kilocode.json | 4 +++- webview-ui/src/i18n/locales/ca/kilocode.json | 4 +++- webview-ui/src/i18n/locales/cs/kilocode.json | 4 +++- webview-ui/src/i18n/locales/de/kilocode.json | 4 +++- webview-ui/src/i18n/locales/en/kilocode.json | 3 ++- webview-ui/src/i18n/locales/es/kilocode.json | 4 +++- webview-ui/src/i18n/locales/fr/kilocode.json | 4 +++- webview-ui/src/i18n/locales/hi/kilocode.json | 4 +++- webview-ui/src/i18n/locales/id/kilocode.json | 4 +++- webview-ui/src/i18n/locales/it/kilocode.json | 4 +++- webview-ui/src/i18n/locales/ja/kilocode.json | 4 +++- webview-ui/src/i18n/locales/ko/kilocode.json | 4 +++- webview-ui/src/i18n/locales/nl/kilocode.json | 4 +++- webview-ui/src/i18n/locales/pl/kilocode.json | 4 +++- webview-ui/src/i18n/locales/pt-BR/kilocode.json | 4 +++- webview-ui/src/i18n/locales/ru/kilocode.json | 4 +++- webview-ui/src/i18n/locales/th/kilocode.json | 4 +++- webview-ui/src/i18n/locales/tr/kilocode.json | 4 +++- webview-ui/src/i18n/locales/uk/kilocode.json | 4 +++- webview-ui/src/i18n/locales/vi/kilocode.json | 4 +++- webview-ui/src/i18n/locales/zh-CN/kilocode.json | 4 +++- webview-ui/src/i18n/locales/zh-TW/kilocode.json | 4 +++- 24 files changed, 69 insertions(+), 33 deletions(-) diff --git a/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx b/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx index 998124e84cf..cfc7a064927 100644 --- a/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx +++ b/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx @@ -195,8 +195,7 @@ export const GhostServiceSettingsView = ({ ) : (
- No suitable autocomplete model found. Please configure a provider in the API - settings. + {t("kilocode:ghost.settings.noModelConfigured")}
)}
diff --git a/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx b/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx index 73370ea5d04..c939f5cef6f 100644 --- a/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx +++ b/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx @@ -267,9 +267,7 @@ describe("GhostServiceSettingsView", () => { }, }) - expect( - screen.getByText(/No suitable autocomplete model found. Please configure a provider in the API settings./), - ).toBeInTheDocument() + expect(screen.getByText(/kilocode:ghost.settings.noModelConfigured/)).toBeInTheDocument() }) it("displays error message when only provider is missing", () => { @@ -281,9 +279,7 @@ describe("GhostServiceSettingsView", () => { }, }) - expect( - screen.getByText(/No suitable autocomplete model found. Please configure a provider in the API settings./), - ).toBeInTheDocument() + expect(screen.getByText(/kilocode:ghost.settings.noModelConfigured/)).toBeInTheDocument() }) it("displays error message when only model is missing", () => { @@ -295,8 +291,6 @@ describe("GhostServiceSettingsView", () => { }, }) - expect( - screen.getByText(/No suitable autocomplete model found. Please configure a provider in the API settings./), - ).toBeInTheDocument() + expect(screen.getByText(/kilocode:ghost.settings.noModelConfigured/)).toBeInTheDocument() }) }) diff --git a/webview-ui/src/i18n/locales/ar/kilocode.json b/webview-ui/src/i18n/locales/ar/kilocode.json index 7c5f6324a21..e253c1bc0e2 100644 --- a/webview-ui/src/i18n/locales/ar/kilocode.json +++ b/webview-ui/src/i18n/locales/ar/kilocode.json @@ -224,7 +224,9 @@ "label": "الإكمال التلقائي اليدوي ({{keybinding}})", "description": "تحتاج إصلاحاً سريعاً أو إكمالاً أو إعادة هيكلة؟ سيستخدم Kilo السياق المحيط لتقديم تحسينات فورية، مما يبقيك في التدفق. عرض الاختصار" }, - "keybindingNotFound": "غير موجود" + "keybindingNotFound": "غير موجود", + "noModelConfigured": "لم يتم العثور على نموذج إكمال تلقائي مناسب. يرجى تكوين مزود في إعدادات API.", + "model": "النموذج" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/ca/kilocode.json b/webview-ui/src/i18n/locales/ca/kilocode.json index 017e91a8a09..a6cecace6a0 100644 --- a/webview-ui/src/i18n/locales/ca/kilocode.json +++ b/webview-ui/src/i18n/locales/ca/kilocode.json @@ -218,7 +218,9 @@ "label": "Autocompleció Manual ({{keybinding}})", "description": "Necessites una correcció ràpida, completació o refactorització? Kilo utilitzarà el context circumdant per oferir millores immediates, mantenint-te en el flux. Veure drecera" }, - "keybindingNotFound": "no trobat" + "keybindingNotFound": "no trobat", + "noModelConfigured": "No s'ha trobat cap model d'autocompletat adequat. Configura un proveïdor a la configuració de l'API.", + "model": "Model" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/cs/kilocode.json b/webview-ui/src/i18n/locales/cs/kilocode.json index 8573aeb948c..78840003c9a 100644 --- a/webview-ui/src/i18n/locales/cs/kilocode.json +++ b/webview-ui/src/i18n/locales/cs/kilocode.json @@ -229,7 +229,9 @@ "label": "Ruční dokončování ({{keybinding}})", "description": "Potřebuješ rychlou opravu, dokončení nebo refaktoring? Kilo použije okolní kontext k nabídnutí okamžitých vylepšení, udržujíc tě v toku. Zobrazit zkratku" }, - "keybindingNotFound": "nenalezeno" + "keybindingNotFound": "nenalezeno", + "noModelConfigured": "Nebyl nalezen žádný vhodný model pro automatické dokončování. Nakonfiguruj prosím poskytovatele v nastavení API.", + "model": "Model" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/de/kilocode.json b/webview-ui/src/i18n/locales/de/kilocode.json index 9a059eef595..8a7effbfee9 100644 --- a/webview-ui/src/i18n/locales/de/kilocode.json +++ b/webview-ui/src/i18n/locales/de/kilocode.json @@ -208,6 +208,7 @@ "title": "Autocomplete", "settings": { "triggers": "Auslöser", + "model": "Modell", "enableAutoTrigger": { "label": "Pausieren zum Vervollständigen", "description": "Wenn aktiviert, löst Kilo Code automatisch Autocomplete aus, wenn du aufhörst zu tippen. Dies kann für schnelle Korrekturen und Vorschläge nützlich sein." @@ -224,7 +225,8 @@ "label": "Manuelle Autovervollständigung ({{keybinding}})", "description": "Brauchst du eine schnelle Korrektur, Vervollständigung oder Refaktorierung? Kilo wird den umgebenden Kontext nutzen, um sofortige Verbesserungen anzubieten und dich im Flow zu halten. Tastenkombination bearbeiten" }, - "keybindingNotFound": "nicht gefunden" + "keybindingNotFound": "nicht gefunden", + "noModelConfigured": "Kein geeignetes Autocomplete-Modell gefunden. Bitte konfiguriere einen Provider in den API-Einstellungen." } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/en/kilocode.json b/webview-ui/src/i18n/locales/en/kilocode.json index d99b31530ad..313570f2be5 100644 --- a/webview-ui/src/i18n/locales/en/kilocode.json +++ b/webview-ui/src/i18n/locales/en/kilocode.json @@ -236,7 +236,8 @@ "label": "Manual Autocomplete ({{keybinding}})", "description": "Need a quick fix, completion, or refactor? Kilo will use the surrounding context to offer immediate improvements, keeping you in the flow. Edit shortcut" }, - "keybindingNotFound": "not found" + "keybindingNotFound": "not found", + "noModelConfigured": "No suitable autocomplete model found. Please configure a provider in the API settings." } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/es/kilocode.json b/webview-ui/src/i18n/locales/es/kilocode.json index c053377c899..10f4b0fc875 100644 --- a/webview-ui/src/i18n/locales/es/kilocode.json +++ b/webview-ui/src/i18n/locales/es/kilocode.json @@ -222,7 +222,9 @@ "label": "Autocompletado Manual ({{keybinding}})", "description": "¿Necesitas una corrección rápida, completado o refactorización? Kilo usará el contexto circundante para ofrecer mejoras inmediatas, manteniéndote en el flujo. Editar atajo" }, - "keybindingNotFound": "no encontrado" + "keybindingNotFound": "no encontrado", + "noModelConfigured": "No se encontró ningún modelo de autocompletado adecuado. Por favor, configura un proveedor en la configuración de API.", + "model": "Modelo" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/fr/kilocode.json b/webview-ui/src/i18n/locales/fr/kilocode.json index 2e4a0ef5a50..da5cd0adb98 100644 --- a/webview-ui/src/i18n/locales/fr/kilocode.json +++ b/webview-ui/src/i18n/locales/fr/kilocode.json @@ -229,7 +229,9 @@ "label": "Saisie automatique manuelle ({{keybinding}})", "description": "Besoin d'une correction rapide, d'un complément ou d'une refactorisation ? Kilo utilisera le contexte environnant pour offrir des améliorations immédiates, te gardant dans le flux. Modifier le raccourci" }, - "keybindingNotFound": "introuvable" + "keybindingNotFound": "introuvable", + "noModelConfigured": "Aucun modèle d'autocomplétion approprié trouvé. Configure un fournisseur dans les paramètres API.", + "model": "Modèle" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/hi/kilocode.json b/webview-ui/src/i18n/locales/hi/kilocode.json index 148a569fd44..4d4f915ef69 100644 --- a/webview-ui/src/i18n/locales/hi/kilocode.json +++ b/webview-ui/src/i18n/locales/hi/kilocode.json @@ -218,7 +218,9 @@ "label": "मैनुअल ऑटोकंप्लीट ({{keybinding}})", "description": "त्वरित सुधार, पूर्णता, या रिफैक्टरिंग चाहिए? Kilo आसपास के संदर्भ का उपयोग करके तत्काल सुधार प्रदान करेगा, आपको प्रवाह में रखते हुए। शॉर्टकट देखें" }, - "keybindingNotFound": "नहीं मिला" + "keybindingNotFound": "नहीं मिला", + "noModelConfigured": "कोई उपयुक्त ऑटोकम्पलीट मॉडल नहीं मिला। कृपया API सेटिंग्स में एक प्रदाता कॉन्फ़िगर करें।", + "model": "मॉडल" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/id/kilocode.json b/webview-ui/src/i18n/locales/id/kilocode.json index 1d60b2670ee..28683e52d59 100644 --- a/webview-ui/src/i18n/locales/id/kilocode.json +++ b/webview-ui/src/i18n/locales/id/kilocode.json @@ -218,7 +218,9 @@ "label": "Pelengkapan Otomatis Manual ({{keybinding}})", "description": "Perlu perbaikan cepat, penyelesaian, atau refaktor? Kilo akan menggunakan konteks sekitar untuk menawarkan perbaikan langsung, menjaga Anda tetap dalam alur. Lihat pintasan" }, - "keybindingNotFound": "tidak ditemukan" + "keybindingNotFound": "tidak ditemukan", + "noModelConfigured": "Tidak ditemukan model autocomplete yang sesuai. Silakan konfigurasi penyedia di pengaturan API.", + "model": "Model" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/it/kilocode.json b/webview-ui/src/i18n/locales/it/kilocode.json index 34864575052..5b34d7c34f8 100644 --- a/webview-ui/src/i18n/locales/it/kilocode.json +++ b/webview-ui/src/i18n/locales/it/kilocode.json @@ -225,7 +225,9 @@ "label": "Completamento Automatico Manuale ({{keybinding}})", "description": "Hai bisogno di una correzione rapida, completamento o refactoring? Kilo userà il contesto circostante per offrire miglioramenti immediati, mantenendoti nel flusso. Modifica scorciatoia" }, - "keybindingNotFound": "non trovato" + "keybindingNotFound": "non trovato", + "noModelConfigured": "Nessun modello di autocompletamento adatto trovato. Configura un provider nelle impostazioni API.", + "model": "Modello" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/ja/kilocode.json b/webview-ui/src/i18n/locales/ja/kilocode.json index 13bf6dc11f0..b58c45c1499 100644 --- a/webview-ui/src/i18n/locales/ja/kilocode.json +++ b/webview-ui/src/i18n/locales/ja/kilocode.json @@ -229,7 +229,9 @@ "label": "手動オートコンプリート ({{keybinding}})", "description": "素早い修正、補完、またはリファクタリングが必要?Kiloは周囲のコンテキストを使用して即座の改善を提供し、フローを維持します。ショートカットを見る" }, - "keybindingNotFound": "見つかりません" + "keybindingNotFound": "見つかりません", + "noModelConfigured": "適切なオートコンプリートモデルが見つかりませんでした。API設定でプロバイダーを設定してください。", + "model": "モデル" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/ko/kilocode.json b/webview-ui/src/i18n/locales/ko/kilocode.json index 411a96dda1a..58805ac2215 100644 --- a/webview-ui/src/i18n/locales/ko/kilocode.json +++ b/webview-ui/src/i18n/locales/ko/kilocode.json @@ -229,7 +229,9 @@ "label": "수동 자동완성({{keybinding}})", "description": "빠른 수정, 완성 또는 리팩토링이 필요하신가요? Kilo가 주변 컨텍스트를 사용하여 즉각적인 개선사항을 제공하여 플로우를 유지합니다. 단축키 보기" }, - "keybindingNotFound": "찾을 수 없음" + "keybindingNotFound": "찾을 수 없음", + "noModelConfigured": "적합한 자동완성 모델을 찾을 수 없습니다. API 설정에서 제공자를 구성하세요.", + "model": "모델" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/nl/kilocode.json b/webview-ui/src/i18n/locales/nl/kilocode.json index 0a094b9caee..05187c4f4aa 100644 --- a/webview-ui/src/i18n/locales/nl/kilocode.json +++ b/webview-ui/src/i18n/locales/nl/kilocode.json @@ -229,7 +229,9 @@ "label": "Handmatige Automatische Aanvulling ({{keybinding}})", "description": "Heb je een snelle fix, voltooiing of refactor nodig? Kilo zal de omringende context gebruiken om onmiddellijke verbeteringen aan te bieden, waardoor je in de flow blijft. Sneltoets bewerken" }, - "keybindingNotFound": "niet gevonden" + "keybindingNotFound": "niet gevonden", + "noModelConfigured": "Geen geschikt autocomplete-model gevonden. Configureer een provider in de API-instellingen.", + "model": "Model" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/pl/kilocode.json b/webview-ui/src/i18n/locales/pl/kilocode.json index b0ad0e7e948..78fdc57c1e6 100644 --- a/webview-ui/src/i18n/locales/pl/kilocode.json +++ b/webview-ui/src/i18n/locales/pl/kilocode.json @@ -222,7 +222,9 @@ "label": "Ręczne uzupełnianie ({{keybinding}})", "description": "Potrzebujesz szybkiej poprawki, uzupełnienia lub refaktoryzacji? Kilo użyje otaczającego kontekstu, aby zaoferować natychmiastowe ulepszenia, utrzymując cię w przepływie. Zobacz skrót" }, - "keybindingNotFound": "nie znaleziono" + "keybindingNotFound": "nie znaleziono", + "noModelConfigured": "Nie znaleziono odpowiedniego modelu autouzupełniania. Skonfiguruj dostawcę w ustawieniach API.", + "model": "Model" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/pt-BR/kilocode.json b/webview-ui/src/i18n/locales/pt-BR/kilocode.json index cc6990924fb..f42895d395b 100644 --- a/webview-ui/src/i18n/locales/pt-BR/kilocode.json +++ b/webview-ui/src/i18n/locales/pt-BR/kilocode.json @@ -225,7 +225,9 @@ "label": "Preenchimento Automático Manual ({{keybinding}})", "description": "Precisa de uma correção rápida, completação ou refatoração? O Kilo usará o contexto ao redor para oferecer melhorias imediatas, mantendo você no fluxo. Ver atalho" }, - "keybindingNotFound": "não encontrado" + "keybindingNotFound": "não encontrado", + "noModelConfigured": "Nenhum modelo de autocompletar adequado encontrado. Configure um provedor nas configurações da API.", + "model": "Modelo" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/ru/kilocode.json b/webview-ui/src/i18n/locales/ru/kilocode.json index 4d52b893ef6..9d32b89c35c 100644 --- a/webview-ui/src/i18n/locales/ru/kilocode.json +++ b/webview-ui/src/i18n/locales/ru/kilocode.json @@ -225,7 +225,9 @@ "label": "Автодополнение вручную ({{keybinding}})", "description": "Нужно быстрое исправление, дополнение или рефакторинг? Kilo использует окружающий контекст для немедленных улучшений, сохраняя тебя в потоке. Посмотреть горячую клавишу" }, - "keybindingNotFound": "не найдено" + "keybindingNotFound": "не найдено", + "noModelConfigured": "Подходящая модель автодополнения не найдена. Настрой провайдера в настройках API.", + "model": "Модель" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/th/kilocode.json b/webview-ui/src/i18n/locales/th/kilocode.json index 5c967f7948b..26e1852e0ee 100644 --- a/webview-ui/src/i18n/locales/th/kilocode.json +++ b/webview-ui/src/i18n/locales/th/kilocode.json @@ -229,7 +229,9 @@ "label": "การเติมอัตโนมัติด้วยตัวเอง ({{keybinding}})", "description": "ต้องการการแก้ไขด่วน การเติมเต็ม หรือการปรับโครงสร้างใหม่? Kilo จะใช้บริบทโดยรอบเพื่อเสนอการปรับปรุงทันที ทำให้คุณอยู่ในขั้นตอนการทำงาน ดูทางลัด" }, - "keybindingNotFound": "ไม่พบ" + "keybindingNotFound": "ไม่พบ", + "noModelConfigured": "ไม่พบโมเดลเติมข้อความอัตโนมัติที่เหมาะสม กรุณาตั้งค่าผู้ให้บริการในการตั้งค่า API", + "model": "โมเดล" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/tr/kilocode.json b/webview-ui/src/i18n/locales/tr/kilocode.json index a386cd6a555..c5ecf5ded5a 100644 --- a/webview-ui/src/i18n/locales/tr/kilocode.json +++ b/webview-ui/src/i18n/locales/tr/kilocode.json @@ -222,7 +222,9 @@ "label": "Manuel Otomatik Tamamlama ({{keybinding}})", "description": "Hızlı bir düzeltme, tamamlama veya yeniden düzenleme mi gerekiyor? Kilo çevredeki bağlamı kullanarak anında iyileştirmeler sunacak, seni akışta tutacak. Kısayolu gör" }, - "keybindingNotFound": "bulunamadı" + "keybindingNotFound": "bulunamadı", + "noModelConfigured": "Uygun otomatik tamamlama modeli bulunamadı. Lütfen API ayarlarında bir sağlayıcı yapılandır.", + "model": "Model" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/uk/kilocode.json b/webview-ui/src/i18n/locales/uk/kilocode.json index a0aa6d229f5..b4c88312115 100644 --- a/webview-ui/src/i18n/locales/uk/kilocode.json +++ b/webview-ui/src/i18n/locales/uk/kilocode.json @@ -229,7 +229,9 @@ "label": "Ручне автозаповнення ({{keybinding}})", "description": "Потрібне швидке виправлення, доповнення або рефакторинг? Kilo використає навколишній контекст для миттєвих покращень, зберігаючи тебе в потоці. Подивитися гарячу клавішу" }, - "keybindingNotFound": "не знайдено" + "keybindingNotFound": "не знайдено", + "noModelConfigured": "Не знайдено відповідної моделі автодоповнення. Налаштуй провайдера в налаштуваннях API.", + "model": "Модель" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/vi/kilocode.json b/webview-ui/src/i18n/locales/vi/kilocode.json index 03934df653b..47cdf06bd33 100644 --- a/webview-ui/src/i18n/locales/vi/kilocode.json +++ b/webview-ui/src/i18n/locales/vi/kilocode.json @@ -225,7 +225,9 @@ "label": "Tự động hoàn thành thủ công ({{keybinding}})", "description": "Cần sửa chữa nhanh, hoàn thành, hoặc tái cấu trúc? Kilo sẽ sử dụng ngữ cảnh xung quanh để cung cấp cải tiến ngay lập tức, giữ bạn trong luồng làm việc. Xem phím tắt" }, - "keybindingNotFound": "không tìm thấy" + "keybindingNotFound": "không tìm thấy", + "noModelConfigured": "Không tìm thấy mô hình tự động hoàn thành phù hợp. Vui lòng cấu hình nhà cung cấp trong cài đặt API.", + "model": "Mô hình" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/zh-CN/kilocode.json b/webview-ui/src/i18n/locales/zh-CN/kilocode.json index 3a0aa3179be..6a55934d45c 100644 --- a/webview-ui/src/i18n/locales/zh-CN/kilocode.json +++ b/webview-ui/src/i18n/locales/zh-CN/kilocode.json @@ -229,7 +229,9 @@ "label": "手动自动完成({{keybinding}})", "description": "需要快速修复、补全或重构?Kilo 将使用周围上下文提供即时改进,保持你的工作流。查看快捷键" }, - "keybindingNotFound": "未找到" + "keybindingNotFound": "未找到", + "noModelConfigured": "未找到合适的自动补全模型。请在 API 设置中配置提供商。", + "model": "模型" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/zh-TW/kilocode.json b/webview-ui/src/i18n/locales/zh-TW/kilocode.json index c6ff79cf0df..ad31266875e 100644 --- a/webview-ui/src/i18n/locales/zh-TW/kilocode.json +++ b/webview-ui/src/i18n/locales/zh-TW/kilocode.json @@ -224,7 +224,9 @@ "label": "手动自动补全 ({{keybinding}})", "description": "需要快速修正、補全或重構?Kilo 將使用周圍內容提供即時改進,保持你的工作流程。檢視快速鍵" }, - "keybindingNotFound": "未找到" + "keybindingNotFound": "未找到", + "noModelConfigured": "找不到合適的自動完成模型。請在 API 設定中配置提供者。", + "model": "模型" } }, "virtualProvider": { From 21ab677d7227bb7e74162157cd9a647ed76908df Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 17:24:09 +0200 Subject: [PATCH 38/46] more translations --- .../components/kilocode/settings/GhostServiceSettings.tsx | 6 ++++-- .../settings/__tests__/GhostServiceSettings.spec.tsx | 4 ++-- webview-ui/src/i18n/locales/ar/kilocode.json | 3 ++- webview-ui/src/i18n/locales/ca/kilocode.json | 3 ++- webview-ui/src/i18n/locales/cs/kilocode.json | 3 ++- webview-ui/src/i18n/locales/de/kilocode.json | 1 + webview-ui/src/i18n/locales/en/kilocode.json | 1 + webview-ui/src/i18n/locales/es/kilocode.json | 3 ++- webview-ui/src/i18n/locales/fr/kilocode.json | 3 ++- webview-ui/src/i18n/locales/hi/kilocode.json | 3 ++- webview-ui/src/i18n/locales/id/kilocode.json | 3 ++- webview-ui/src/i18n/locales/it/kilocode.json | 3 ++- webview-ui/src/i18n/locales/ja/kilocode.json | 3 ++- webview-ui/src/i18n/locales/ko/kilocode.json | 3 ++- webview-ui/src/i18n/locales/nl/kilocode.json | 3 ++- webview-ui/src/i18n/locales/pl/kilocode.json | 3 ++- webview-ui/src/i18n/locales/pt-BR/kilocode.json | 3 ++- webview-ui/src/i18n/locales/ru/kilocode.json | 3 ++- webview-ui/src/i18n/locales/th/kilocode.json | 3 ++- webview-ui/src/i18n/locales/tr/kilocode.json | 3 ++- webview-ui/src/i18n/locales/uk/kilocode.json | 3 ++- webview-ui/src/i18n/locales/vi/kilocode.json | 3 ++- webview-ui/src/i18n/locales/zh-CN/kilocode.json | 3 ++- webview-ui/src/i18n/locales/zh-TW/kilocode.json | 3 ++- 24 files changed, 48 insertions(+), 24 deletions(-) diff --git a/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx b/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx index cfc7a064927..1a03b6e1cbb 100644 --- a/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx +++ b/webview-ui/src/components/kilocode/settings/GhostServiceSettings.tsx @@ -187,10 +187,12 @@ export const GhostServiceSettingsView = ({ {provider && model ? ( <>
- Provider: {provider} + {t("kilocode:ghost.settings.provider")}:{" "} + {provider}
- Model: {model} + {t("kilocode:ghost.settings.model")}:{" "} + {model}
) : ( diff --git a/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx b/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx index c939f5cef6f..bfdae3faef9 100644 --- a/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx +++ b/webview-ui/src/components/kilocode/settings/__tests__/GhostServiceSettings.spec.tsx @@ -252,9 +252,9 @@ describe("GhostServiceSettingsView", () => { }, }) - expect(screen.getByText(/Provider:/)).toBeInTheDocument() + expect(screen.getByText(/kilocode:ghost.settings.provider/)).toBeInTheDocument() expect(screen.getByText(/openrouter/)).toBeInTheDocument() - expect(screen.getByText(/Model:/)).toBeInTheDocument() + expect(screen.getAllByText(/kilocode:ghost.settings.model/).length).toBeGreaterThan(0) expect(screen.getByText(/openai\/gpt-4o-mini/)).toBeInTheDocument() }) diff --git a/webview-ui/src/i18n/locales/ar/kilocode.json b/webview-ui/src/i18n/locales/ar/kilocode.json index e253c1bc0e2..477eb2531a5 100644 --- a/webview-ui/src/i18n/locales/ar/kilocode.json +++ b/webview-ui/src/i18n/locales/ar/kilocode.json @@ -226,7 +226,8 @@ }, "keybindingNotFound": "غير موجود", "noModelConfigured": "لم يتم العثور على نموذج إكمال تلقائي مناسب. يرجى تكوين مزود في إعدادات API.", - "model": "النموذج" + "model": "النموذج", + "provider": "المزود" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/ca/kilocode.json b/webview-ui/src/i18n/locales/ca/kilocode.json index a6cecace6a0..599c6801f31 100644 --- a/webview-ui/src/i18n/locales/ca/kilocode.json +++ b/webview-ui/src/i18n/locales/ca/kilocode.json @@ -220,7 +220,8 @@ }, "keybindingNotFound": "no trobat", "noModelConfigured": "No s'ha trobat cap model d'autocompletat adequat. Configura un proveïdor a la configuració de l'API.", - "model": "Model" + "model": "Model", + "provider": "Proveïdor" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/cs/kilocode.json b/webview-ui/src/i18n/locales/cs/kilocode.json index 78840003c9a..49e31606e6e 100644 --- a/webview-ui/src/i18n/locales/cs/kilocode.json +++ b/webview-ui/src/i18n/locales/cs/kilocode.json @@ -231,7 +231,8 @@ }, "keybindingNotFound": "nenalezeno", "noModelConfigured": "Nebyl nalezen žádný vhodný model pro automatické dokončování. Nakonfiguruj prosím poskytovatele v nastavení API.", - "model": "Model" + "model": "Model", + "provider": "Poskytovatel" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/de/kilocode.json b/webview-ui/src/i18n/locales/de/kilocode.json index 8a7effbfee9..e612d3a3fc5 100644 --- a/webview-ui/src/i18n/locales/de/kilocode.json +++ b/webview-ui/src/i18n/locales/de/kilocode.json @@ -209,6 +209,7 @@ "settings": { "triggers": "Auslöser", "model": "Modell", + "provider": "Provider", "enableAutoTrigger": { "label": "Pausieren zum Vervollständigen", "description": "Wenn aktiviert, löst Kilo Code automatisch Autocomplete aus, wenn du aufhörst zu tippen. Dies kann für schnelle Korrekturen und Vorschläge nützlich sein." diff --git a/webview-ui/src/i18n/locales/en/kilocode.json b/webview-ui/src/i18n/locales/en/kilocode.json index 313570f2be5..3863677ac86 100644 --- a/webview-ui/src/i18n/locales/en/kilocode.json +++ b/webview-ui/src/i18n/locales/en/kilocode.json @@ -220,6 +220,7 @@ "settings": { "triggers": "Triggers", "model": "Model", + "provider": "Provider", "enableAutoTrigger": { "label": "Pause to Complete", "description": "When enabled, Kilo Code will automatically trigger Autocomplete when you pause typing. This can be useful for quick fixes and suggestions." diff --git a/webview-ui/src/i18n/locales/es/kilocode.json b/webview-ui/src/i18n/locales/es/kilocode.json index 10f4b0fc875..267c0ce2292 100644 --- a/webview-ui/src/i18n/locales/es/kilocode.json +++ b/webview-ui/src/i18n/locales/es/kilocode.json @@ -224,7 +224,8 @@ }, "keybindingNotFound": "no encontrado", "noModelConfigured": "No se encontró ningún modelo de autocompletado adecuado. Por favor, configura un proveedor en la configuración de API.", - "model": "Modelo" + "model": "Modelo", + "provider": "Proveedor" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/fr/kilocode.json b/webview-ui/src/i18n/locales/fr/kilocode.json index da5cd0adb98..e7eb8cfa327 100644 --- a/webview-ui/src/i18n/locales/fr/kilocode.json +++ b/webview-ui/src/i18n/locales/fr/kilocode.json @@ -231,7 +231,8 @@ }, "keybindingNotFound": "introuvable", "noModelConfigured": "Aucun modèle d'autocomplétion approprié trouvé. Configure un fournisseur dans les paramètres API.", - "model": "Modèle" + "model": "Modèle", + "provider": "Fournisseur" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/hi/kilocode.json b/webview-ui/src/i18n/locales/hi/kilocode.json index 4d4f915ef69..2d693c562ee 100644 --- a/webview-ui/src/i18n/locales/hi/kilocode.json +++ b/webview-ui/src/i18n/locales/hi/kilocode.json @@ -220,7 +220,8 @@ }, "keybindingNotFound": "नहीं मिला", "noModelConfigured": "कोई उपयुक्त ऑटोकम्पलीट मॉडल नहीं मिला। कृपया API सेटिंग्स में एक प्रदाता कॉन्फ़िगर करें।", - "model": "मॉडल" + "model": "मॉडल", + "provider": "प्रदाता" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/id/kilocode.json b/webview-ui/src/i18n/locales/id/kilocode.json index 28683e52d59..559c219ccd8 100644 --- a/webview-ui/src/i18n/locales/id/kilocode.json +++ b/webview-ui/src/i18n/locales/id/kilocode.json @@ -220,7 +220,8 @@ }, "keybindingNotFound": "tidak ditemukan", "noModelConfigured": "Tidak ditemukan model autocomplete yang sesuai. Silakan konfigurasi penyedia di pengaturan API.", - "model": "Model" + "model": "Model", + "provider": "Penyedia" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/it/kilocode.json b/webview-ui/src/i18n/locales/it/kilocode.json index 5b34d7c34f8..052e5588877 100644 --- a/webview-ui/src/i18n/locales/it/kilocode.json +++ b/webview-ui/src/i18n/locales/it/kilocode.json @@ -227,7 +227,8 @@ }, "keybindingNotFound": "non trovato", "noModelConfigured": "Nessun modello di autocompletamento adatto trovato. Configura un provider nelle impostazioni API.", - "model": "Modello" + "model": "Modello", + "provider": "Provider" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/ja/kilocode.json b/webview-ui/src/i18n/locales/ja/kilocode.json index b58c45c1499..d39cce7cf18 100644 --- a/webview-ui/src/i18n/locales/ja/kilocode.json +++ b/webview-ui/src/i18n/locales/ja/kilocode.json @@ -231,7 +231,8 @@ }, "keybindingNotFound": "見つかりません", "noModelConfigured": "適切なオートコンプリートモデルが見つかりませんでした。API設定でプロバイダーを設定してください。", - "model": "モデル" + "model": "モデル", + "provider": "プロバイダー" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/ko/kilocode.json b/webview-ui/src/i18n/locales/ko/kilocode.json index 58805ac2215..e27ad805608 100644 --- a/webview-ui/src/i18n/locales/ko/kilocode.json +++ b/webview-ui/src/i18n/locales/ko/kilocode.json @@ -231,7 +231,8 @@ }, "keybindingNotFound": "찾을 수 없음", "noModelConfigured": "적합한 자동완성 모델을 찾을 수 없습니다. API 설정에서 제공자를 구성하세요.", - "model": "모델" + "model": "모델", + "provider": "제공자" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/nl/kilocode.json b/webview-ui/src/i18n/locales/nl/kilocode.json index 05187c4f4aa..de53002333b 100644 --- a/webview-ui/src/i18n/locales/nl/kilocode.json +++ b/webview-ui/src/i18n/locales/nl/kilocode.json @@ -231,7 +231,8 @@ }, "keybindingNotFound": "niet gevonden", "noModelConfigured": "Geen geschikt autocomplete-model gevonden. Configureer een provider in de API-instellingen.", - "model": "Model" + "model": "Model", + "provider": "Provider" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/pl/kilocode.json b/webview-ui/src/i18n/locales/pl/kilocode.json index 78fdc57c1e6..2cf988a41e7 100644 --- a/webview-ui/src/i18n/locales/pl/kilocode.json +++ b/webview-ui/src/i18n/locales/pl/kilocode.json @@ -224,7 +224,8 @@ }, "keybindingNotFound": "nie znaleziono", "noModelConfigured": "Nie znaleziono odpowiedniego modelu autouzupełniania. Skonfiguruj dostawcę w ustawieniach API.", - "model": "Model" + "model": "Model", + "provider": "Dostawca" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/pt-BR/kilocode.json b/webview-ui/src/i18n/locales/pt-BR/kilocode.json index f42895d395b..019b484d0db 100644 --- a/webview-ui/src/i18n/locales/pt-BR/kilocode.json +++ b/webview-ui/src/i18n/locales/pt-BR/kilocode.json @@ -227,7 +227,8 @@ }, "keybindingNotFound": "não encontrado", "noModelConfigured": "Nenhum modelo de autocompletar adequado encontrado. Configure um provedor nas configurações da API.", - "model": "Modelo" + "model": "Modelo", + "provider": "Provedor" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/ru/kilocode.json b/webview-ui/src/i18n/locales/ru/kilocode.json index 9d32b89c35c..b8ac0f0f1ad 100644 --- a/webview-ui/src/i18n/locales/ru/kilocode.json +++ b/webview-ui/src/i18n/locales/ru/kilocode.json @@ -227,7 +227,8 @@ }, "keybindingNotFound": "не найдено", "noModelConfigured": "Подходящая модель автодополнения не найдена. Настрой провайдера в настройках API.", - "model": "Модель" + "model": "Модель", + "provider": "Провайдер" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/th/kilocode.json b/webview-ui/src/i18n/locales/th/kilocode.json index 26e1852e0ee..7ccec0192f2 100644 --- a/webview-ui/src/i18n/locales/th/kilocode.json +++ b/webview-ui/src/i18n/locales/th/kilocode.json @@ -231,7 +231,8 @@ }, "keybindingNotFound": "ไม่พบ", "noModelConfigured": "ไม่พบโมเดลเติมข้อความอัตโนมัติที่เหมาะสม กรุณาตั้งค่าผู้ให้บริการในการตั้งค่า API", - "model": "โมเดล" + "model": "โมเดล", + "provider": "ผู้ให้บริการ" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/tr/kilocode.json b/webview-ui/src/i18n/locales/tr/kilocode.json index c5ecf5ded5a..7794c7a1d3e 100644 --- a/webview-ui/src/i18n/locales/tr/kilocode.json +++ b/webview-ui/src/i18n/locales/tr/kilocode.json @@ -224,7 +224,8 @@ }, "keybindingNotFound": "bulunamadı", "noModelConfigured": "Uygun otomatik tamamlama modeli bulunamadı. Lütfen API ayarlarında bir sağlayıcı yapılandır.", - "model": "Model" + "model": "Model", + "provider": "Sağlayıcı" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/uk/kilocode.json b/webview-ui/src/i18n/locales/uk/kilocode.json index b4c88312115..1c84801eaee 100644 --- a/webview-ui/src/i18n/locales/uk/kilocode.json +++ b/webview-ui/src/i18n/locales/uk/kilocode.json @@ -231,7 +231,8 @@ }, "keybindingNotFound": "не знайдено", "noModelConfigured": "Не знайдено відповідної моделі автодоповнення. Налаштуй провайдера в налаштуваннях API.", - "model": "Модель" + "model": "Модель", + "provider": "Провайдер" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/vi/kilocode.json b/webview-ui/src/i18n/locales/vi/kilocode.json index 47cdf06bd33..7d664fea906 100644 --- a/webview-ui/src/i18n/locales/vi/kilocode.json +++ b/webview-ui/src/i18n/locales/vi/kilocode.json @@ -227,7 +227,8 @@ }, "keybindingNotFound": "không tìm thấy", "noModelConfigured": "Không tìm thấy mô hình tự động hoàn thành phù hợp. Vui lòng cấu hình nhà cung cấp trong cài đặt API.", - "model": "Mô hình" + "model": "Mô hình", + "provider": "Nhà cung cấp" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/zh-CN/kilocode.json b/webview-ui/src/i18n/locales/zh-CN/kilocode.json index 6a55934d45c..78f26bb92ab 100644 --- a/webview-ui/src/i18n/locales/zh-CN/kilocode.json +++ b/webview-ui/src/i18n/locales/zh-CN/kilocode.json @@ -231,7 +231,8 @@ }, "keybindingNotFound": "未找到", "noModelConfigured": "未找到合适的自动补全模型。请在 API 设置中配置提供商。", - "model": "模型" + "model": "模型", + "provider": "Provider" } }, "virtualProvider": { diff --git a/webview-ui/src/i18n/locales/zh-TW/kilocode.json b/webview-ui/src/i18n/locales/zh-TW/kilocode.json index ad31266875e..91e89b394fd 100644 --- a/webview-ui/src/i18n/locales/zh-TW/kilocode.json +++ b/webview-ui/src/i18n/locales/zh-TW/kilocode.json @@ -226,7 +226,8 @@ }, "keybindingNotFound": "未找到", "noModelConfigured": "找不到合適的自動完成模型。請在 API 設定中配置提供者。", - "model": "模型" + "model": "模型", + "provider": "Provider" } }, "virtualProvider": { From 9c83b5d2b17cc2a84feafc9baa4db1d9a087096a Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 16 Oct 2025 17:31:18 +0200 Subject: [PATCH 39/46] fix --- packages/types/src/provider-settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 3a88dfffb4a..775bc48ffdf 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -1,7 +1,7 @@ import { z } from "zod" import { modelInfoSchema, reasoningEffortWithMinimalSchema, verbosityLevelsSchema, serviceTierSchema } from "./model.js" -import { checkKilocodeBalance } from "./kilocode.js" +import { checkKilocodeBalance } from "./kilocode/kilocode.js" import { codebaseIndexProviderSchema } from "./codebase-index.js" import { anthropicModels, From c626bdca4f057d927c8d741659aadd410027530f Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 17 Oct 2025 10:24:47 +0200 Subject: [PATCH 40/46] reload autocomplete after changing settings --- .../webview/__tests__/webviewMessageHandler.autoSwitch.spec.ts | 3 +++ src/core/webview/webviewMessageHandler.ts | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/core/webview/__tests__/webviewMessageHandler.autoSwitch.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.autoSwitch.spec.ts index 9db7fdd2a1c..ef3b997ea39 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.autoSwitch.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.autoSwitch.spec.ts @@ -27,6 +27,9 @@ vi.mock("vscode", () => ({ uriScheme: "vscode", openExternal: vi.fn(), }, + commands: { + executeCommand: vi.fn(), + }, })) vi.mock("axios") diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 3845816e748..07b52a7efd6 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2032,6 +2032,8 @@ export const webviewMessageHandler = async ( await provider.providerSettingsManager.saveConfig(message.text, message.apiConfiguration) const listApiConfig = await provider.providerSettingsManager.listConfig() await updateGlobalState("listApiConfigMeta", listApiConfig) + // Reload ghost model when API provider settings change + vscode.commands.executeCommand("kilo-code.ghost.reload") } catch (error) { provider.log( `Error save api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, From a41b712d22532710cf724578b0204a510176d64c Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 17 Oct 2025 10:42:28 +0200 Subject: [PATCH 41/46] fix bug in balance check --- packages/types/src/kilocode/kilocode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/kilocode/kilocode.ts b/packages/types/src/kilocode/kilocode.ts index b9b09c930a5..2b24e89cc7a 100644 --- a/packages/types/src/kilocode/kilocode.ts +++ b/packages/types/src/kilocode/kilocode.ts @@ -75,7 +75,7 @@ export async function checkKilocodeBalance(kilocodeToken: string): Promise 0 } catch (error) { console.error("Error checking kilocode balance:", error) From 9e3e2d41331436ea35b912fb7a21549c33d97c95 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 17 Oct 2025 10:48:46 +0200 Subject: [PATCH 42/46] less complex types --- packages/types/src/provider-settings.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 775bc48ffdf..1868b044767 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -168,8 +168,7 @@ export const AUTOCOMPLETE_PROVIDER_MODELS = { mistral: "codestral-latest", kilocode: "mistralai/codestral-2508", openrouter: "mistralai/codestral-2508", -} as const satisfies Record, string> - +} as const export type AutocompleteProviderKey = keyof typeof AUTOCOMPLETE_PROVIDER_MODELS interface ProviderSettingsManager { From a47d6ca4ff99c44c60d5e0a95449e034b17c1f41 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 17 Oct 2025 11:14:09 +0200 Subject: [PATCH 43/46] refactor(types): relocate autocomplete provider config to kilocode --- packages/types/src/kilocode/kilocode.ts | 46 +++++++++++++++++++++++++ packages/types/src/provider-settings.ts | 46 ------------------------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/packages/types/src/kilocode/kilocode.ts b/packages/types/src/kilocode/kilocode.ts index 2b24e89cc7a..8bf70b45f20 100644 --- a/packages/types/src/kilocode/kilocode.ts +++ b/packages/types/src/kilocode/kilocode.ts @@ -1,4 +1,5 @@ import { z } from "zod" +import { ProviderSettings, ProviderSettingsEntry } from "../provider-settings.js" export const ghostServiceSettingsSchema = z .object({ @@ -82,3 +83,48 @@ export async function checkKilocodeBalance(kilocodeToken: string): Promise + getProfile(params: { id: string }): Promise +} + +export type ProviderUsabilityChecker = ( + provider: AutocompleteProviderKey, + providerSettingsManager: ProviderSettingsManager, +) => Promise + +export const defaultProviderUsabilityChecker: ProviderUsabilityChecker = async (provider, providerSettingsManager) => { + if (provider === "kilocode") { + try { + const profiles = await providerSettingsManager.listConfig() + const kilocodeProfile = profiles.find((p) => p.apiProvider === "kilocode") + + if (!kilocodeProfile) { + return false + } + + const profile = await providerSettingsManager.getProfile({ id: kilocodeProfile.id }) + const kilocodeToken = profile.kilocodeToken + + if (!kilocodeToken) { + return false + } + + return await checkKilocodeBalance(kilocodeToken) + } catch (error) { + console.error("Error checking kilocode balance:", error) + return false + } + } + + // For all other providers, assume they are usable + return true +} diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 1868b044767..0dfb7f19c75 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { modelInfoSchema, reasoningEffortWithMinimalSchema, verbosityLevelsSchema, serviceTierSchema } from "./model.js" -import { checkKilocodeBalance } from "./kilocode/kilocode.js" import { codebaseIndexProviderSchema } from "./codebase-index.js" import { anthropicModels, @@ -164,51 +163,6 @@ export type ProviderName = z.infer export const isProviderName = (key: unknown): key is ProviderName => typeof key === "string" && providerNames.includes(key as ProviderName) -export const AUTOCOMPLETE_PROVIDER_MODELS = { - mistral: "codestral-latest", - kilocode: "mistralai/codestral-2508", - openrouter: "mistralai/codestral-2508", -} as const -export type AutocompleteProviderKey = keyof typeof AUTOCOMPLETE_PROVIDER_MODELS - -interface ProviderSettingsManager { - listConfig(): Promise - getProfile(params: { id: string }): Promise -} - -export type ProviderUsabilityChecker = ( - provider: AutocompleteProviderKey, - providerSettingsManager: ProviderSettingsManager, -) => Promise - -export const defaultProviderUsabilityChecker: ProviderUsabilityChecker = async (provider, providerSettingsManager) => { - if (provider === "kilocode") { - try { - const profiles = await providerSettingsManager.listConfig() - const kilocodeProfile = profiles.find((p) => p.apiProvider === "kilocode") - - if (!kilocodeProfile) { - return false - } - - const profile = await providerSettingsManager.getProfile({ id: kilocodeProfile.id }) - const kilocodeToken = profile.kilocodeToken - - if (!kilocodeToken) { - return false - } - - return await checkKilocodeBalance(kilocodeToken) - } catch (error) { - console.error("Error checking kilocode balance:", error) - return false - } - } - - // For all other providers, assume they are usable - return true -} - /** * ProviderSettingsEntry */ From 4dd1cd1067550efc45a20583a2740e99fc6e0ab7 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 17 Oct 2025 11:22:14 +0200 Subject: [PATCH 44/46] add org support --- packages/types/src/__tests__/kilocode.test.ts | 115 +++++++++++++++++- packages/types/src/kilocode/kilocode.ts | 18 ++- 2 files changed, 126 insertions(+), 7 deletions(-) diff --git a/packages/types/src/__tests__/kilocode.test.ts b/packages/types/src/__tests__/kilocode.test.ts index d69d1aeedb9..7a4d55d80b7 100644 --- a/packages/types/src/__tests__/kilocode.test.ts +++ b/packages/types/src/__tests__/kilocode.test.ts @@ -1,7 +1,7 @@ // npx vitest run src/__tests__/kilocode.test.ts -import { describe, it, expect } from "vitest" -import { ghostServiceSettingsSchema } from "../kilocode/kilocode.js" +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" +import { ghostServiceSettingsSchema, checkKilocodeBalance } from "../kilocode/kilocode.js" describe("ghostServiceSettingsSchema", () => { describe("autoTriggerDelay", () => { @@ -93,3 +93,114 @@ describe("ghostServiceSettingsSchema", () => { }) }) }) + +describe("checkKilocodeBalance", () => { + const mockToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbnYiOiJwcm9kdWN0aW9uIn0.test" + const mockOrgId = "org-123" + + beforeEach(() => { + global.fetch = vi.fn() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it("should return true when balance is positive", async () => { + vi.mocked(global.fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({ balance: 100 }), + } as Response) + + const result = await checkKilocodeBalance(mockToken) + expect(result).toBe(true) + expect(global.fetch).toHaveBeenCalledWith( + "https://api.kilocode.ai/api/profile/balance", + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: `Bearer ${mockToken}`, + }), + }), + ) + }) + + it("should return false when balance is zero", async () => { + vi.mocked(global.fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({ balance: 0 }), + } as Response) + + const result = await checkKilocodeBalance(mockToken) + expect(result).toBe(false) + }) + + it("should return false when balance is negative", async () => { + vi.mocked(global.fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({ balance: -10 }), + } as Response) + + const result = await checkKilocodeBalance(mockToken) + expect(result).toBe(false) + }) + + it("should include organization ID in headers when provided", async () => { + vi.mocked(global.fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({ balance: 100 }), + } as Response) + + const result = await checkKilocodeBalance(mockToken, mockOrgId) + expect(result).toBe(true) + expect(global.fetch).toHaveBeenCalledWith( + "https://api.kilocode.ai/api/profile/balance", + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: `Bearer ${mockToken}`, + "X-KiloCode-OrganizationId": mockOrgId, + }), + }), + ) + }) + + it("should not include organization ID in headers when not provided", async () => { + vi.mocked(global.fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({ balance: 100 }), + } as Response) + + await checkKilocodeBalance(mockToken) + + const fetchCall = vi.mocked(global.fetch).mock.calls[0] + const headers = (fetchCall[1] as RequestInit)?.headers as Record + + expect(headers).toHaveProperty("Authorization") + expect(headers).not.toHaveProperty("X-KiloCode-OrganizationId") + }) + + it("should return false when API request fails", async () => { + vi.mocked(global.fetch).mockResolvedValueOnce({ + ok: false, + } as Response) + + const result = await checkKilocodeBalance(mockToken) + expect(result).toBe(false) + }) + + it("should return false when fetch throws an error", async () => { + vi.mocked(global.fetch).mockRejectedValueOnce(new Error("Network error")) + + const result = await checkKilocodeBalance(mockToken) + expect(result).toBe(false) + }) + + it("should handle missing balance field in response", async () => { + vi.mocked(global.fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({}), + } as Response) + + const result = await checkKilocodeBalance(mockToken) + expect(result).toBe(false) + }) +}) diff --git a/packages/types/src/kilocode/kilocode.ts b/packages/types/src/kilocode/kilocode.ts index 8bf70b45f20..11506d2fb15 100644 --- a/packages/types/src/kilocode/kilocode.ts +++ b/packages/types/src/kilocode/kilocode.ts @@ -59,16 +59,23 @@ export function getKiloBaseUriFromToken(kilocodeToken?: string) { /** * Check if the Kilocode account has a positive balance * @param kilocodeToken - The Kilocode JWT token + * @param kilocodeOrganizationId - Optional organization ID to include in headers * @returns Promise - True if balance > 0, false otherwise */ -export async function checkKilocodeBalance(kilocodeToken: string): Promise { +export async function checkKilocodeBalance(kilocodeToken: string, kilocodeOrganizationId?: string): Promise { try { const baseUrl = getKiloBaseUriFromToken(kilocodeToken) + const headers: Record = { + Authorization: `Bearer ${kilocodeToken}`, + } + + if (kilocodeOrganizationId) { + headers["X-KiloCode-OrganizationId"] = kilocodeOrganizationId + } + const response = await fetch(`${baseUrl}/api/profile/balance`, { - headers: { - Authorization: `Bearer ${kilocodeToken}`, - }, + headers, }) if (!response.ok) { @@ -113,12 +120,13 @@ export const defaultProviderUsabilityChecker: ProviderUsabilityChecker = async ( const profile = await providerSettingsManager.getProfile({ id: kilocodeProfile.id }) const kilocodeToken = profile.kilocodeToken + const kilocodeOrgId = profile.kilocodeOrganizationId if (!kilocodeToken) { return false } - return await checkKilocodeBalance(kilocodeToken) + return await checkKilocodeBalance(kilocodeToken, kilocodeOrgId) } catch (error) { console.error("Error checking kilocode balance:", error) return false From 87766971622a00df3416f804bae0ed25a8e02d5b Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 17 Oct 2025 11:25:18 +0200 Subject: [PATCH 45/46] mark kilocode change --- src/core/webview/webviewMessageHandler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 07b52a7efd6..c98064e7617 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2032,8 +2032,7 @@ export const webviewMessageHandler = async ( await provider.providerSettingsManager.saveConfig(message.text, message.apiConfiguration) const listApiConfig = await provider.providerSettingsManager.listConfig() await updateGlobalState("listApiConfigMeta", listApiConfig) - // Reload ghost model when API provider settings change - vscode.commands.executeCommand("kilo-code.ghost.reload") + vscode.commands.executeCommand("kilo-code.ghost.reload") // kilocode_change: Reload ghost model when API provider settings change } catch (error) { provider.log( `Error save api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, From c5711a1a71ed828b4031cf9134ac0ef5db2dac2a Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Fri, 17 Oct 2025 11:29:03 +0200 Subject: [PATCH 46/46] fix --- packages/types/src/__tests__/kilocode.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/types/src/__tests__/kilocode.test.ts b/packages/types/src/__tests__/kilocode.test.ts index 7a4d55d80b7..63983d1300a 100644 --- a/packages/types/src/__tests__/kilocode.test.ts +++ b/packages/types/src/__tests__/kilocode.test.ts @@ -172,7 +172,8 @@ describe("checkKilocodeBalance", () => { await checkKilocodeBalance(mockToken) const fetchCall = vi.mocked(global.fetch).mock.calls[0] - const headers = (fetchCall[1] as RequestInit)?.headers as Record + expect(fetchCall).toBeDefined() + const headers = (fetchCall![1] as RequestInit)?.headers as Record expect(headers).toHaveProperty("Authorization") expect(headers).not.toHaveProperty("X-KiloCode-OrganizationId")