From 47a994f835b62fd50556fe54a594e704613118c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Wed, 4 Feb 2026 13:07:06 +0100 Subject: [PATCH 1/5] feat(models): add kilo/auto model with centralized configuration Extract kilo/auto model constants into a shared module and inject the model into both OpenRouter hooks and provider lists. This ensures the auto-routing model appears consistently across the application with unified metadata (context length, pricing, description). - Create kilo-auto-model.ts with centralized model constants - Add buildKiloAutoModel() and buildAutoModel() factory functions - Update API route to use KILO_AUTO_MODEL_ID constant - Inject auto model into useOpenRouterModelsAndProviders hook - Add auto model to enhancedModelList with preferredIndex -1 - Update test snapshot to include kilo/auto model entry --- src/app/api/openrouter/[...path]/route.ts | 9 ++-- src/app/api/openrouter/hooks.ts | 42 ++++++++++++++++- src/lib/kilo-auto-model.ts | 13 ++++++ src/lib/providers/openrouter/index.ts | 46 ++++++++++++++++++- .../openrouter-models-sorting.approved.json | 37 +++++++++++++++ 5 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 src/lib/kilo-auto-model.ts diff --git a/src/app/api/openrouter/[...path]/route.ts b/src/app/api/openrouter/[...path]/route.ts index 60bec65689..f6726bb2f4 100644 --- a/src/app/api/openrouter/[...path]/route.ts +++ b/src/app/api/openrouter/[...path]/route.ts @@ -49,11 +49,10 @@ import { } from '@/lib/anonymous'; import { checkFreeModelRateLimit, logFreeModelRequest } from '@/lib/free-model-rate-limiter'; import { classifyAbuse } from '@/lib/abuse-service'; +import { KILO_AUTO_MODEL_ID } from '@/lib/kilo-auto-model'; const MAX_TOKENS_LIMIT = 99999999999; // GPT4.1 default is ~32k -const AUTO_MODEL_ID = 'kilo/auto'; - const OPUS = 'anthropic/claude-opus-4.5'; const SONNET = 'anthropic/claude-sonnet-4.5'; @@ -119,7 +118,7 @@ export async function POST(request: NextRequest): Promise({ queryKey: ['openrouter-models'], @@ -169,7 +207,9 @@ export function useOpenRouterModelsAndProviders() { } } - return [...modelBySlug.values()]; + const modelsWithEndpoints = [...modelBySlug.values()]; + const hasAutoAlready = modelsWithEndpoints.some(model => model.slug === KILO_AUTO_MODEL_ID); + return hasAutoAlready ? modelsWithEndpoints : [buildKiloAutoModel(), ...modelsWithEndpoints]; }, [query.data]); return { diff --git a/src/lib/kilo-auto-model.ts b/src/lib/kilo-auto-model.ts new file mode 100644 index 0000000000..756139ae68 --- /dev/null +++ b/src/lib/kilo-auto-model.ts @@ -0,0 +1,13 @@ +export const KILO_AUTO_MODEL_ID = 'kilo/auto'; + +export const KILO_AUTO_MODEL_NAME = 'Kilo: Auto'; + +export const KILO_AUTO_MODEL_DESCRIPTION = + 'Automatically routes your request to the best model for the task.'; + +export const KILO_AUTO_MODEL_CONTEXT_LENGTH = 200_000; +export const KILO_AUTO_MODEL_MAX_COMPLETION_TOKENS = 64_000; + +// Keep non-zero so "limited access" UIs don't treat it as free. +export const KILO_AUTO_MODEL_PROMPT_PRICE = '0.0000010'; +export const KILO_AUTO_MODEL_COMPLETION_PRICE = '0.0000010'; diff --git a/src/lib/providers/openrouter/index.ts b/src/lib/providers/openrouter/index.ts index a09777a301..b037545d53 100644 --- a/src/lib/providers/openrouter/index.ts +++ b/src/lib/providers/openrouter/index.ts @@ -9,6 +9,15 @@ import { errorExceptInTest } from '@/lib/utils.server'; import { captureException, captureMessage } from '@sentry/nextjs'; import { convertFromKiloModel } from '@/lib/providers/kilo-free-model'; import { getModelSettings, getVersionedModelSettings } from '@/lib/providers/recommended-models'; +import { + KILO_AUTO_MODEL_COMPLETION_PRICE, + KILO_AUTO_MODEL_CONTEXT_LENGTH, + KILO_AUTO_MODEL_DESCRIPTION, + KILO_AUTO_MODEL_ID, + KILO_AUTO_MODEL_MAX_COMPLETION_TOKENS, + KILO_AUTO_MODEL_NAME, + KILO_AUTO_MODEL_PROMPT_PRICE, +} from '@/lib/kilo-auto-model'; // Re-export from shared module for backwards compatibility export { normalizeModelId } from '@/lib/model-utils'; @@ -17,7 +26,37 @@ export function isRateLimitedToDeathFree(model: string) { return model.endsWith(':free') && !isFreeModel(model); } +function buildAutoModel(): OpenRouterModel { + return { + id: KILO_AUTO_MODEL_ID, + name: KILO_AUTO_MODEL_NAME, + created: 0, + description: KILO_AUTO_MODEL_DESCRIPTION, + architecture: { + input_modalities: ['text'], + output_modalities: ['text'], + tokenizer: 'Other', + }, + top_provider: { + is_moderated: false, + context_length: KILO_AUTO_MODEL_CONTEXT_LENGTH, + max_completion_tokens: KILO_AUTO_MODEL_MAX_COMPLETION_TOKENS, + }, + pricing: { + prompt: KILO_AUTO_MODEL_PROMPT_PRICE, + completion: KILO_AUTO_MODEL_COMPLETION_PRICE, + request: '0', + image: '0', + web_search: '0', + internal_reasoning: '0', + }, + context_length: KILO_AUTO_MODEL_CONTEXT_LENGTH, + supported_parameters: ['max_tokens', 'temperature', 'tools', 'reasoning', 'include_reasoning'], + }; +} + function enhancedModelList(models: OpenRouterModel[]) { + const autoModel = buildAutoModel(); const enhancedModels = models .filter( (model: OpenRouterModel) => @@ -25,14 +64,17 @@ function enhancedModelList(models: OpenRouterModel[]) { !kiloFreeModels.some(m => m.public_id === model.id && m.is_enabled) ) .concat(kiloFreeModels.filter(m => m.is_enabled).map(model => convertFromKiloModel(model))) + .concat([autoModel]) .map((model: OpenRouterModel) => { - const preferredIndex = preferredModels.indexOf(model.id); + const preferredIndex = + model.id === KILO_AUTO_MODEL_ID ? -1 : preferredModels.indexOf(model.id); const ageDays = (Date.now() / 1_000 - model.created) / (24 * 3600); const isNew = preferredIndex >= 0 && ageDays >= 0 && ageDays < 7; return { ...model, name: isNew ? model.name + ' (new)' : model.name, - preferredIndex: preferredIndex >= 0 ? preferredIndex : undefined, + preferredIndex: + preferredIndex >= 0 || model.id === KILO_AUTO_MODEL_ID ? preferredIndex : undefined, settings: getModelSettings(model.id), versioned_settings: getVersionedModelSettings(model.id), }; diff --git a/src/tests/openrouter-models-sorting.approved.json b/src/tests/openrouter-models-sorting.approved.json index ca62d2d5c9..93e757b058 100644 --- a/src/tests/openrouter-models-sorting.approved.json +++ b/src/tests/openrouter-models-sorting.approved.json @@ -1,5 +1,42 @@ { "data": [ + { + "id": "kilo/auto", + "name": "Kilo: Auto", + "created": 0, + "description": "Routes to a real model based on the `x-kilocode-mode` header. This is a Kilo-only pseudo-model id; it is not a real upstream model.", + "architecture": { + "input_modalities": [ + "text" + ], + "output_modalities": [ + "text" + ], + "tokenizer": "Other" + }, + "top_provider": { + "is_moderated": false, + "context_length": 200000, + "max_completion_tokens": 64000 + }, + "pricing": { + "prompt": "0.0000010", + "completion": "0.0000010", + "request": "0", + "image": "0", + "web_search": "0", + "internal_reasoning": "0" + }, + "context_length": 200000, + "supported_parameters": [ + "max_tokens", + "temperature", + "tools", + "reasoning", + "include_reasoning" + ], + "preferredIndex": -1 + }, { "id": "minimax/minimax-m2.1:free", "canonical_slug": "minimax/minimax-m2.1:free", From a898ac6cf14ba0a4b20b5f05cf46a9c1f2b505bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Wed, 4 Feb 2026 13:11:32 +0100 Subject: [PATCH 2/5] test(models): update auto model description and reindex preferred models Update the kilo/auto model description to be more user-friendly and adjust preferredIndex values for all models to accommodate the new auto model at position 0. --- src/tests/openrouter-models-sorting.approved.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tests/openrouter-models-sorting.approved.json b/src/tests/openrouter-models-sorting.approved.json index 93e757b058..dcf394c906 100644 --- a/src/tests/openrouter-models-sorting.approved.json +++ b/src/tests/openrouter-models-sorting.approved.json @@ -4,7 +4,7 @@ "id": "kilo/auto", "name": "Kilo: Auto", "created": 0, - "description": "Routes to a real model based on the `x-kilocode-mode` header. This is a Kilo-only pseudo-model id; it is not a real upstream model.", + "description": "Automatically routes your request to the best model for the task.", "architecture": { "input_modalities": [ "text" @@ -78,7 +78,7 @@ "include_reasoning" ], "default_parameters": {}, - "preferredIndex": 0, + "preferredIndex": 1, "settings": { "included_tools": [ "search_and_replace" @@ -130,7 +130,7 @@ "include_reasoning" ], "default_parameters": {}, - "preferredIndex": 1, + "preferredIndex": 2, "versioned_settings": { "4.146.0": { "included_tools": [ @@ -186,7 +186,7 @@ "include_reasoning" ], "default_parameters": {}, - "preferredIndex": 3, + "preferredIndex": 4, "versioned_settings": { "4.146.0": { "included_tools": [ @@ -238,7 +238,7 @@ "tools" ], "default_parameters": {}, - "preferredIndex": 4 + "preferredIndex": 5 }, { "id": "anthropic/claude-sonnet-4", From 27b29065479d793fa07bb9a97440f21897d7d474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Wed, 4 Feb 2026 13:12:05 +0100 Subject: [PATCH 3/5] feat(models): add kilo/auto model to recommended models list Add KILO_AUTO_MODEL_ID as the first entry in the recommended models array with tool_choice_required and random_vercel_routing both set to false. --- src/lib/providers/recommended-models.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/providers/recommended-models.ts b/src/lib/providers/recommended-models.ts index 3eba8a93dd..875a05d57c 100644 --- a/src/lib/providers/recommended-models.ts +++ b/src/lib/providers/recommended-models.ts @@ -1,4 +1,5 @@ import type { ModelSettings, VersionedSettings } from '@/lib/organizations/organization-types'; +import { KILO_AUTO_MODEL_ID } from '@/lib/kilo-auto-model'; import { giga_potato_model } from '@/lib/providers/gigapotato'; import { minimax_m21_free_model } from '@/lib/providers/minimax'; import { zai_glm47_free_model } from '@/lib/providers/zai'; @@ -10,6 +11,11 @@ export type RecommendedModel = { }; export const recommendedModels = [ + { + public_id: KILO_AUTO_MODEL_ID, + tool_choice_required: false, + random_vercel_routing: false, + }, { public_id: minimax_m21_free_model.is_enabled ? minimax_m21_free_model.public_id From 33cecae3dfd3a87e432211df92a7e15b51c9736a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Wed, 4 Feb 2026 13:12:20 +0100 Subject: [PATCH 4/5] feat(models): enable random vercel routing for kilo/auto model --- src/lib/providers/recommended-models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/providers/recommended-models.ts b/src/lib/providers/recommended-models.ts index 875a05d57c..d057b8f09a 100644 --- a/src/lib/providers/recommended-models.ts +++ b/src/lib/providers/recommended-models.ts @@ -14,7 +14,7 @@ export const recommendedModels = [ { public_id: KILO_AUTO_MODEL_ID, tool_choice_required: false, - random_vercel_routing: false, + random_vercel_routing: true, }, { public_id: minimax_m21_free_model.is_enabled From 5b43f9e8b2cefd0e09f56b023c4207df86382b19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Wed, 4 Feb 2026 13:33:56 +0100 Subject: [PATCH 5/5] fix(api): apply model restriction check to auto model requests Previously, the model restriction check was incorrectly skipped when requestedAutoModel was true due to inverted logic. This fix ensures that organization model allow list validation is always performed, including for kilo/auto model requests. --- src/app/api/openrouter/[...path]/route.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/app/api/openrouter/[...path]/route.ts b/src/app/api/openrouter/[...path]/route.ts index f6726bb2f4..4444ab98e3 100644 --- a/src/app/api/openrouter/[...path]/route.ts +++ b/src/app/api/openrouter/[...path]/route.ts @@ -282,13 +282,11 @@ export async function POST(request: NextRequest): Promise