From c31cc4dbd1993743b514aa067a01ffc0683568be Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 7 Apr 2026 14:34:03 -0400 Subject: [PATCH 1/5] perf(cli): parallelize Kilo + Apertis model fetches in ModelsDev.get() --- packages/opencode/src/provider/models.ts | 36 ++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index 5d97f0939b3..7ddd9d90e41 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -149,11 +149,9 @@ export namespace ModelsDev { if (kiloAllowed && !providers["kilo"]) { const kiloOptions = config.provider?.kilo?.options - // kilocode_change start - resolve org ID from auth (OAuth accountId) not just config const kiloAuth = await Auth.get("kilo") const kiloOrgId = kiloOptions?.kilocodeOrganizationId ?? (kiloAuth?.type === "oauth" ? kiloAuth.accountId : undefined) - // kilocode_change end const normalizedBaseURL = normalizeKiloBaseURL(kiloOptions?.baseURL, kiloOrgId) const kiloFetchOptions = { ...(normalizedBaseURL ? { baseURL: normalizedBaseURL } : {}), @@ -164,7 +162,19 @@ export namespace ModelsDev { : "https://api.kilo.ai/api/openrouter" const providerBaseURL = normalizedBaseURL ?? defaultBaseURL const ensureTrailingSlash = (value: string): string => (value.endsWith("/") ? value : `${value}/`) - const kiloModels = await ModelCache.fetch("kilo", kiloFetchOptions).catch(() => ({})) + const apertisConfig = config.provider?.apertis?.options + const apertisBaseURL = apertisConfig?.baseURL ?? "https://api.apertis.ai/v1" + const apertisFetchOptions = { + ...(apertisConfig?.baseURL ? { baseURL: apertisConfig.baseURL } : {}), + } + + const [kiloModels, apertisModels] = await Promise.all([ + ModelCache.fetch("kilo", kiloFetchOptions).catch(() => ({})), + !providers["apertis"] + ? ModelCache.fetch("apertis", apertisFetchOptions).catch(() => ({})) + : Promise.resolve(null), + ]) + providers["kilo"] = { id: "kilo", name: "Kilo Gateway", @@ -176,12 +186,22 @@ export namespace ModelsDev { if (Object.keys(kiloModels).length === 0) { ModelCache.refresh("kilo", kiloFetchOptions).catch(() => {}) } - } - // Inject Apertis provider with dynamic model fetching - if (!providers["apertis"]) { - const apertisConfigObj = await Config.get() - const apertisConfig = apertisConfigObj.provider?.apertis?.options + if (!providers["apertis"] && apertisModels !== null) { + providers["apertis"] = { + id: "apertis", + name: "Apertis", + env: ["APERTIS_API_KEY"], + api: apertisBaseURL, + npm: "@ai-sdk/openai-compatible", + models: apertisModels, + } + if (Object.keys(apertisModels).length === 0) { + ModelCache.refresh("apertis", apertisFetchOptions).catch(() => {}) + } + } + } else if (!providers["apertis"]) { + const apertisConfig = config.provider?.apertis?.options const apertisBaseURL = apertisConfig?.baseURL ?? "https://api.apertis.ai/v1" const apertisFetchOptions = { ...(apertisConfig?.baseURL ? { baseURL: apertisConfig.baseURL } : {}), From c86e36e072855f68605f7471b979465adf2d58f8 Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 7 Apr 2026 14:50:22 -0400 Subject: [PATCH 2/5] perf(vscode): replace webview polling with extensionDataReady signal --- packages/kilo-vscode/src/KiloProvider.ts | 1 + .../webview-ui/src/context/config.tsx | 37 ++++++++------- .../webview-ui/src/context/provider.tsx | 37 ++++++++------- .../webview-ui/src/context/session.tsx | 46 ++++++++----------- .../webview-ui/src/types/messages.ts | 5 ++ 5 files changed, 66 insertions(+), 60 deletions(-) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 2b153315bb4..a71e26d5c9f 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -1142,6 +1142,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper ]) this.sendNotificationSettings() this.sendTimelineSetting() + this.postMessage({ type: "extensionDataReady" }) // Start polling worktree diff stats for the sidebar badge this.startStatsPolling() diff --git a/packages/kilo-vscode/webview-ui/src/context/config.tsx b/packages/kilo-vscode/webview-ui/src/context/config.tsx index 015c4934bbf..4f98a1b957c 100644 --- a/packages/kilo-vscode/webview-ui/src/context/config.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/config.tsx @@ -8,7 +8,8 @@ * changes into a single write (which triggers disposeAll on the CLI). */ -import { createContext, useContext, createSignal, onCleanup, ParentComponent, Accessor } from "solid-js" +import { createContext, useContext, createSignal, onCleanup } from "solid-js" +import type { ParentComponent, Accessor } from "solid-js" import { useVSCode } from "./vscode" import type { Config, ExtensionMessage } from "../types/messages" import { deepMerge, stripNulls, resolveConfig } from "../utils/config-utils" @@ -71,25 +72,29 @@ export const ConfigProvider: ParentComponent = (props) => { onCleanup(unsubscribe) - // Request config in case the initial push was missed. - // Retry a few times because the extension's httpClient may - // not be ready yet when the first request arrives. - let retries = 0 - const maxRetries = 5 - const retryMs = 500 - + // Request config immediately; if the extension's httpClient is not yet ready, + // extensionDataReady will fire once initialization completes and we retry once. vscode.postMessage({ type: "requestConfig" }) - const retryTimer = setInterval(() => { - retries++ - if (!loading() || retries >= maxRetries) { - clearInterval(retryTimer) - return + const fallback = setTimeout(() => { + if (loading()) { + vscode.postMessage({ type: "requestConfig" }) } - vscode.postMessage({ type: "requestConfig" }) - }, retryMs) + }, 3000) - onCleanup(() => clearInterval(retryTimer)) + const unsubReady = vscode.onMessage((message: ExtensionMessage) => { + if (message.type !== "extensionDataReady") return + unsubReady() + clearTimeout(fallback) + if (loading()) { + vscode.postMessage({ type: "requestConfig" }) + } + }) + + onCleanup(() => { + unsubReady() + clearTimeout(fallback) + }) function updateConfig(partial: Partial) { // Optimistically update local state with deep merge + null stripping diff --git a/packages/kilo-vscode/webview-ui/src/context/provider.tsx b/packages/kilo-vscode/webview-ui/src/context/provider.tsx index f5f95d2d915..4b173b8baa0 100644 --- a/packages/kilo-vscode/webview-ui/src/context/provider.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/provider.tsx @@ -4,7 +4,8 @@ * Selection is now per-session — see session.tsx. */ -import { createContext, useContext, createSignal, createMemo, onCleanup, ParentComponent, Accessor } from "solid-js" +import { createContext, useContext, createSignal, createMemo, onCleanup } from "solid-js" +import type { ParentComponent, Accessor } from "solid-js" import { useVSCode } from "./vscode" import type { Provider, ProviderModel, ModelSelection, ExtensionMessage, ProviderAuthState } from "../types/messages" import type { ProviderAuthMethod } from "@kilocode/sdk/v2/client" @@ -64,25 +65,29 @@ export const ProviderProvider: ParentComponent = (props) => { onCleanup(unsubscribe) - // Request providers in case the initial push was missed. - // Retry a few times because the extension's httpClient may - // not be ready yet when the first request arrives. - let retries = 0 - const maxRetries = 5 - const retryMs = 500 - + // Request providers immediately; if the extension's httpClient is not yet ready, + // extensionDataReady will fire once initialization completes and we retry once. vscode.postMessage({ type: "requestProviders" }) - const retryTimer = setInterval(() => { - retries++ - if (Object.keys(providers()).length > 0 || retries >= maxRetries) { - clearInterval(retryTimer) - return + const fallback = setTimeout(() => { + if (Object.keys(providers()).length === 0) { + vscode.postMessage({ type: "requestProviders" }) } - vscode.postMessage({ type: "requestProviders" }) - }, retryMs) + }, 3000) - onCleanup(() => clearInterval(retryTimer)) + const unsubReady = vscode.onMessage((message: ExtensionMessage) => { + if (message.type !== "extensionDataReady") return + unsubReady() + clearTimeout(fallback) + if (Object.keys(providers()).length === 0) { + vscode.postMessage({ type: "requestProviders" }) + } + }) + + onCleanup(() => { + unsubReady() + clearTimeout(fallback) + }) const value: ProviderContextValue = { providers, diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 3699decc2a6..78f885ea37b 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -496,24 +496,10 @@ export const SessionProvider: ParentComponent = (props) => { }) }) - // Request agents in case the initial push was missed. - // Retry a few times because the extension's httpClient may - // not be ready yet when the first request arrives. - let agentRetries = 0 - const agentMaxRetries = 5 - const agentRetryMs = 500 - + // Request agents immediately; if the extension's httpClient is not yet ready, + // extensionDataReady will fire once initialization completes and we retry once. vscode.postMessage({ type: "requestAgents" }) - const agentRetryTimer = setInterval(() => { - agentRetries++ - if (agents().length > 0 || agentRetries >= agentMaxRetries) { - clearInterval(agentRetryTimer) - return - } - vscode.postMessage({ type: "requestAgents" }) - }, agentRetryMs) - // Skills loaded from the CLI backend const unsubSkills = vscode.onMessage((message: ExtensionMessage) => { if (message.type === "skillsLoaded") { @@ -538,24 +524,28 @@ export const SessionProvider: ParentComponent = (props) => { } }) - // Request MCP status on init with retry (same pattern as agents) - let mcpRetries = 0 + // Request MCP status immediately; retry once on extensionDataReady if still missing. vscode.postMessage({ type: "requestMcpStatus" }) - const mcpRetryTimer = setInterval(() => { - mcpRetries++ - if (Object.keys(mcpStatus()).length > 0 || mcpRetries >= 5) { - clearInterval(mcpRetryTimer) - return - } - vscode.postMessage({ type: "requestMcpStatus" }) - }, 500) + + const fallback = setTimeout(() => { + if (agents().length === 0) vscode.postMessage({ type: "requestAgents" }) + if (Object.keys(mcpStatus()).length === 0) vscode.postMessage({ type: "requestMcpStatus" }) + }, 3000) + + const unsubReady = vscode.onMessage((message: ExtensionMessage) => { + if (message.type !== "extensionDataReady") return + unsubReady() + clearTimeout(fallback) + if (agents().length === 0) vscode.postMessage({ type: "requestAgents" }) + if (Object.keys(mcpStatus()).length === 0) vscode.postMessage({ type: "requestMcpStatus" }) + }) onCleanup(() => { unsubAgents() unsubSkills() unsubMcpStatus() - clearInterval(agentRetryTimer) - clearInterval(mcpRetryTimer) + unsubReady() + clearTimeout(fallback) }) // Variant (thinking effort) selection — keyed by "providerID/modelID" diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index 0b4f0f348c8..c7ea46d59a8 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -1196,6 +1196,10 @@ export interface ClearPendingPromptsMessage { type: "clearPendingPrompts" } +export interface ExtensionDataReadyMessage { + type: "extensionDataReady" +} + // ============================================ // Marketplace Messages // ============================================ @@ -1387,6 +1391,7 @@ export type ExtensionMessage = | WorktreeStatsLoadedMessage | McpStatusLoadedMessage | ClearPendingPromptsMessage + | ExtensionDataReadyMessage // ============================================ // Messages FROM webview TO extension From 971f4042d7c58fd469e8f6b9fb8605ee808393cc Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 7 Apr 2026 15:07:08 -0400 Subject: [PATCH 3/5] fix: pin framer-motion to 12.34.5 to match motion-dom@12.34.3 --- bun.lock | 3 ++- package.json | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index eeaccd23638..81a32838330 100644 --- a/bun.lock +++ b/bun.lock @@ -582,6 +582,7 @@ "diff": "8.0.4", "dompurify": "3.3.3", "fastify": ">=5.8.3", + "framer-motion": "12.34.5", "happy-dom": ">=20.8.9", "lodash": "4.18.1", "path-to-regexp": ">=8.4.0", @@ -2926,7 +2927,7 @@ "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], - "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], + "framer-motion": ["framer-motion@12.34.5", "", { "dependencies": { "motion-dom": "^12.34.5", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-Z2dQ+o7BsfpJI3+u0SQUNCrN+ajCKJen1blC4rCHx1Ta2EOHs+xKJegLT2aaD9iSMbU3OoX+WabQXkloUbZmJQ=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], diff --git a/package.json b/package.json index 7c626177b8e..c067d19066b 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "overrides": { "@types/bun": "catalog:", "@types/node": "catalog:", + "framer-motion": "12.34.5", "path-to-regexp": ">=8.4.0", "picomatch": ">=2.3.2", "defu": "6.1.6", From e5349dc7e3c4d58cde3c3da4f0fcfb672450da05 Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 7 Apr 2026 16:20:11 -0400 Subject: [PATCH 4/5] Revert "fix: pin framer-motion to 12.34.5 to match motion-dom@12.34.3" This reverts commit 971f4042d7c58fd469e8f6b9fb8605ee808393cc. --- bun.lock | 3 +-- package.json | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 95881f57154..19f273a6ac1 100644 --- a/bun.lock +++ b/bun.lock @@ -582,7 +582,6 @@ "diff": "8.0.4", "dompurify": "3.3.3", "fastify": ">=5.8.3", - "framer-motion": "12.34.5", "happy-dom": ">=20.8.9", "lodash": "4.18.1", "path-to-regexp": ">=8.4.0", @@ -2927,7 +2926,7 @@ "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], - "framer-motion": ["framer-motion@12.34.5", "", { "dependencies": { "motion-dom": "^12.34.5", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-Z2dQ+o7BsfpJI3+u0SQUNCrN+ajCKJen1blC4rCHx1Ta2EOHs+xKJegLT2aaD9iSMbU3OoX+WabQXkloUbZmJQ=="], + "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], diff --git a/package.json b/package.json index 26d9410d2a8..654595cb6e9 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,6 @@ "overrides": { "@types/bun": "catalog:", "@types/node": "catalog:", - "framer-motion": "12.34.5", "path-to-regexp": ">=8.4.0", "picomatch": ">=2.3.2", "defu": "6.1.6", From 6b81526c1c39d3827968457aa1bdba5a686c8e85 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 8 Apr 2026 12:29:19 -0400 Subject: [PATCH 5/5] fix(cli): resolve org ID from OAuth auth accountId with comment --- packages/opencode/src/provider/models.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index 7ddd9d90e41..4b143e18e1b 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -149,6 +149,7 @@ export namespace ModelsDev { if (kiloAllowed && !providers["kilo"]) { const kiloOptions = config.provider?.kilo?.options + // resolve org ID from auth (OAuth accountId) not just config const kiloAuth = await Auth.get("kilo") const kiloOrgId = kiloOptions?.kilocodeOrganizationId ?? (kiloAuth?.type === "oauth" ? kiloAuth.accountId : undefined)