Merge pull request #8528 from Kilo-Org/caring-columnist

perf: targeted startup performance wins (parallel model fetches + event-driven webview hydration)
This commit is contained in:
Kirill Kalishev
2026-04-08 12:32:57 -04:00
committed by GitHub
6 changed files with 95 additions and 68 deletions
+1
View File
@@ -1168,6 +1168,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()
@@ -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<Config>) {
// Optimistically update local state with deep merge + null stripping
@@ -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,
@@ -503,24 +503,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") {
@@ -545,24 +531,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"
@@ -1283,6 +1283,10 @@ export interface ClearPendingPromptsMessage {
type: "clearPendingPrompts"
}
export interface ExtensionDataReadyMessage {
type: "extensionDataReady"
}
// ============================================
// Marketplace Messages
// ============================================
@@ -1475,6 +1479,7 @@ export type ExtensionMessage =
| WorktreeStatsLoadedMessage
| McpStatusLoadedMessage
| ClearPendingPromptsMessage
| ExtensionDataReadyMessage
// ============================================
// Messages FROM webview TO extension
+29 -8
View File
@@ -149,11 +149,10 @@ 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
// 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 +163,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 +187,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 } : {}),