From ae954cd690f8cf2ccfd171ccd10d7ce833a1dcb2 Mon Sep 17 00:00:00 2001 From: "matterai-app[bot]" Date: Tue, 15 Sep 2026 17:56:36 +0530 Subject: [PATCH 1/2] fix(kilocode): resolve the default model from the live catalog and account plan getKilocodeDefaultModel no longer returns the hardcoded openRouterDefaultModelId. It fetches /v1/web/models and /axoncode/profile in parallel and returns the entry the backend flags freePlan for free accounts, or the first catalog entry (index 0) for every other plan, with a 10-minute TTL cache and a fallback to openRouterDefaultModelId on failure. The stale-model reset paths in ClineProvider now pass the Kilocode token and organization id. --- CHANGELOG.md | 6 + .../kilocode/getKilocodeDefaultModel.ts | 137 ++++++++++++------ src/core/webview/ClineProvider.ts | 10 +- 3 files changed, 110 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e7c342166..c26993f3fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [v6.8.6] - 2026-09-15 + +### Fixed + +- **Plan-aware default model.** `getKilocodeDefaultModel` no longer returns the hardcoded `openRouterDefaultModelId`. It now resolves the default from the live MatterAI catalog (`/v1/web/models`): free accounts get the entry the backend flags `freePlan`, every other plan gets the first catalog entry (index 0, ordered by the catalog's `sortOrder`). The account plan comes from `/axoncode/profile`; a failed profile fetch is treated as free, and any catalog failure still falls back to `openRouterDefaultModelId`. The stale-model reset paths in `ClineProvider` now pass the KiloCode token and organization id so they resolve the same plan-aware default instead of the hardcoded one. + ## [v6.8.5] - 2026-09-04 ### Added diff --git a/src/api/providers/kilocode/getKilocodeDefaultModel.ts b/src/api/providers/kilocode/getKilocodeDefaultModel.ts index ab8f2bad4f..d2e8a5c358 100644 --- a/src/api/providers/kilocode/getKilocodeDefaultModel.ts +++ b/src/api/providers/kilocode/getKilocodeDefaultModel.ts @@ -9,52 +9,104 @@ type KilocodeToken = string type OrganizationId = string -const cache = new Map>() +const cache = new Map; expiresAt: number }>() -const defaultsSchema = z.object({ - defaultModel: z.string().nullish(), +// The default follows the account plan, so a cached value must expire: a plan +// change (or a catalog edit) has to be picked up without an extension reload. +const CACHE_TTL_MS = 10 * 60 * 1000 + +const catalogSchema = z.object({ + data: z.array( + z.object({ + id: z.string(), + freePlan: z.boolean().nullish(), + }), + ), +}) + +const profileSchema = z.object({ + plan: z.string().nullish(), + tieredUsage: z.object({ plan: z.string().nullish() }).nullish(), }) const fetcher = fetchWithTimeout(5000) +const CATALOG_URL = "https://api.matterai.so/v1/web/models" +const PROFILE_URL = "https://api.matterai.so/axoncode/profile" + +/** Whether an AxonCode plan string is the free tier (a missing plan counts as free). */ +function isFreePlan(plan?: string | null): boolean { + const normalized = plan?.trim().toLowerCase() ?? "" + return normalized === "" || normalized === "free" +} + +/** + * Resolves the default model from the live MatterAI catalog: free accounts get + * the entry the backend flags `freePlan`, every other plan gets the first entry + * the backend serves (index 0, ordered by the catalog's `sortOrder`). + */ +async function fetchCatalogDefaultModel( + kilocodeToken: KilocodeToken, + providerSettings?: ProviderSettings, +): Promise { + const headers: Record = { + ...DEFAULT_HEADERS, + Authorization: `Bearer ${kilocodeToken}`, + } + + // Add X-KILOCODE-TESTER: SUPPRESS header if the setting is enabled + if ( + providerSettings?.kilocodeTesterWarningsDisabledUntil && + providerSettings.kilocodeTesterWarningsDisabledUntil > Date.now() + ) { + headers["X-KILOCODE-TESTER"] = "SUPPRESS" + } + + const [catalogResponse, profileResponse] = await Promise.all([ + fetcher(getKiloUrlFromToken(CATALOG_URL, kilocodeToken), { headers }), + fetcher(getKiloUrlFromToken(PROFILE_URL, kilocodeToken), { headers }), + ]) + + if (!catalogResponse.ok) { + throw new Error(`Fetching model catalog from ${CATALOG_URL} failed: ${catalogResponse.status}`) + } + + const catalog = await catalogSchema.parseAsync(await catalogResponse.json()) + const models = catalog.data.filter((model) => !model.id.startsWith("axon-")) + if (models.length === 0) { + throw new Error(`Model catalog from ${CATALOG_URL} was empty`) + } + + // A failed profile fetch must not block the default: an unknown plan is + // treated as free, which still resolves to a usable catalog entry. + const plan = profileResponse.ok + ? ((await profileSchema.parseAsync(await profileResponse.json())).plan ?? undefined) + : undefined + + if (isFreePlan(plan)) { + const freeModel = models.find((model) => model.freePlan === true) + if (freeModel) { + console.info(`Default model for the free plan: ${freeModel.id}`) + return freeModel.id + } + } + + console.info(`Default model from ${CATALOG_URL}: ${models[0]!.id}`) + return models[0]!.id +} + async function fetchKilocodeDefaultModel( - _kilocodeToken: KilocodeToken, + kilocodeToken: KilocodeToken, _organizationId?: OrganizationId, - _providerSettings?: ProviderSettings, + providerSettings?: ProviderSettings, ): Promise { - return openRouterDefaultModelId - // try { - // const path = organizationId ? `/organizations/${organizationId}/defaults` : `/defaults` - // const url = getKiloUrlFromToken(`https://api.matterai.so${path}`, kilocodeToken) - - // const headers: Record = { - // ...DEFAULT_HEADERS, - // Authorization: `Bearer ${kilocodeToken}`, - // } - - // // Add X-KILOCODE-TESTER: SUPPRESS header if the setting is enabled - // if ( - // providerSettings?.kilocodeTesterWarningsDisabledUntil && - // providerSettings.kilocodeTesterWarningsDisabledUntil > Date.now() - // ) { - // headers["X-KILOCODE-TESTER"] = "SUPPRESS" - // } - - // const response = await fetcher(url, { headers }) - // if (!response.ok) { - // throw new Error(`Fetching default model from ${url} failed: ${response.status}`) - // } - // const defaultModel = (await defaultsSchema.parseAsync(await response.json())).defaultModel - // if (!defaultModel) { - // throw new Error(`Default model from ${url} was empty`) - // } - // console.info(`Fetched default model from ${url}: ${defaultModel}`) - // return defaultModel - // } catch (err) { - // console.error("Failed to get default model", err) - // TelemetryService.instance.captureException(err, { context: "getKilocodeDefaultModel" }) - // return openRouterDefaultModelId - // } + try { + return await fetchCatalogDefaultModel(kilocodeToken, providerSettings) + } catch (err) { + console.error("Failed to get default model", err) + TelemetryService.instance.captureException(err, { context: "getKilocodeDefaultModel" }) + return openRouterDefaultModelId + } } export async function getKilocodeDefaultModel( @@ -71,9 +123,12 @@ export async function getKilocodeDefaultModel( testerSuppressed: providerSettings?.kilocodeTesterWarningsDisabledUntil, }) let defaultModelPromise = cache.get(key) - if (!defaultModelPromise) { - defaultModelPromise = fetchKilocodeDefaultModel(kilocodeToken, organizationId, providerSettings) + if (!defaultModelPromise || defaultModelPromise.expiresAt <= Date.now()) { + defaultModelPromise = { + promise: fetchKilocodeDefaultModel(kilocodeToken, organizationId, providerSettings), + expiresAt: Date.now() + CACHE_TTL_MS, + } cache.set(key, defaultModelPromise) } - return await defaultModelPromise + return await defaultModelPromise.promise } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index c5ec1c13ca..9d1121374b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2573,7 +2573,10 @@ ${prompt} if (apiConfiguration?.apiProvider === "kilocode" && apiConfiguration?.kilocodeModel) { if (!isValidKilocodeModel(apiConfiguration.kilocodeModel)) { const staleModel = apiConfiguration.kilocodeModel - const defaultModel = await getKilocodeDefaultModel() + const defaultModel = await getKilocodeDefaultModel( + apiConfiguration.kilocodeToken, + apiConfiguration.kilocodeOrganizationId, + ) const updatedConfig = { ...apiConfiguration, kilocodeModel: defaultModel } await this.contextProxy.setProviderSettings(updatedConfig) mergedApiConfiguration = { ...mergedApiConfiguration, kilocodeModel: defaultModel } @@ -2802,7 +2805,10 @@ ${prompt} if (providerSettings?.apiProvider === "kilocode" && providerSettings?.kilocodeModel) { if (!isValidKilocodeModel(providerSettings.kilocodeModel)) { const staleModel = providerSettings.kilocodeModel - const defaultModel = await getKilocodeDefaultModel() + const defaultModel = await getKilocodeDefaultModel( + providerSettings.kilocodeToken, + providerSettings.kilocodeOrganizationId, + ) providerSettings.kilocodeModel = defaultModel await this.contextProxy.setProviderSettings(providerSettings) this.log( From e9afa68d11eec6dea9051ae1aa46bbd34f63737e Mon Sep 17 00:00:00 2001 From: "matterai-app[bot]" Date: Tue, 15 Sep 2026 17:56:45 +0530 Subject: [PATCH 2/2] chore(release): v6.8.6 --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 36fc2767a9..5843ba27ed 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "matterai", - "version": "6.8.5", + "version": "6.8.6", "icon": "assets/icons/matterai-ic.png", "galleryBanner": { "color": "#FFFFFF",