From 20ed66b7c181387796c910b2771572a4d6e6c1a2 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:21:17 +0000 Subject: [PATCH 1/2] Exclude slackbot-only models from /openrouter/models by default Add includeSlackbotOnly query parameter to opt in to including slackbot-only models in the response. The useModelSelectorList hook accepts an optional { includeSlackbotOnly } parameter that passes the query param through to the API. --- src/app/api/openrouter/hooks.ts | 12 +++++++++--- src/app/api/openrouter/models/route.ts | 5 +++-- src/lib/providers/openrouter/index.ts | 16 ++++++++++++---- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/app/api/openrouter/hooks.ts b/src/app/api/openrouter/hooks.ts index b55d508d6..b3bee8773 100644 --- a/src/app/api/openrouter/hooks.ts +++ b/src/app/api/openrouter/hooks.ts @@ -106,12 +106,18 @@ async function parseModelsByProviderBackupData() { ); } -export function useModelSelectorList(organizationId: string | undefined) { +export function useModelSelectorList( + organizationId: string | undefined, + { includeSlackbotOnly = false }: { includeSlackbotOnly?: boolean } = {} +) { const query = useQuery({ - queryKey: ['openrouter-models', organizationId], + queryKey: ['openrouter-models', organizationId, { includeSlackbotOnly }], queryFn: async (): Promise => { + const modelsUrl = includeSlackbotOnly + ? '/api/openrouter/models?includeSlackbotOnly=true' + : '/api/openrouter/models'; const response = await fetch( - organizationId ? `/api/organizations/${organizationId}/models` : '/api/openrouter/models' + organizationId ? `/api/organizations/${organizationId}/models` : modelsUrl ); if (!response.ok) { throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`); diff --git a/src/app/api/openrouter/models/route.ts b/src/app/api/openrouter/models/route.ts index 63db2d9e4..75b186e42 100644 --- a/src/app/api/openrouter/models/route.ts +++ b/src/app/api/openrouter/models/route.ts @@ -11,10 +11,11 @@ export const revalidate = 60; * curl -vvv 'http://localhost:3000/api/openrouter/models' */ export async function GET( - _request: NextRequest + request: NextRequest ): Promise> { try { - const data = await getEnhancedOpenRouterModels(); + const includeSlackbotOnly = request.nextUrl.searchParams.get('includeSlackbotOnly') === 'true'; + const data = await getEnhancedOpenRouterModels({ includeSlackbotOnly }); return NextResponse.json(data); } catch (error) { captureException(error, { diff --git a/src/lib/providers/openrouter/index.ts b/src/lib/providers/openrouter/index.ts index 105c02ce9..220c6461b 100644 --- a/src/lib/providers/openrouter/index.ts +++ b/src/lib/providers/openrouter/index.ts @@ -51,14 +51,20 @@ function buildAutoModel(): OpenRouterModel { }; } -function enhancedModelList(models: OpenRouterModel[]) { +function enhancedModelList( + models: OpenRouterModel[], + { includeSlackbotOnly = false }: { includeSlackbotOnly?: boolean } = {} +) { const autoModel = buildAutoModel(); + const freeModelsToInclude = kiloFreeModels.filter( + m => m.is_enabled && (includeSlackbotOnly || !m.slackbot_only) + ); const enhancedModels = models .filter( (model: OpenRouterModel) => !kiloFreeModels.some(m => m.public_id === model.id && m.is_enabled) ) - .concat(kiloFreeModels.filter(m => m.is_enabled).map(model => convertFromKiloModel(model))) + .concat(freeModelsToInclude.map(model => convertFromKiloModel(model))) .concat([autoModel]) .map((model: OpenRouterModel) => { const preferredIndex = @@ -149,7 +155,9 @@ export async function getRawOpenRouterModels(): Promise { +export async function getEnhancedOpenRouterModels( + options: { includeSlackbotOnly?: boolean } = {} +): Promise { const rawResponse = await getRawOpenRouterModels(); // If data is not in expected format (e.g., validation failed), return as-is @@ -157,5 +165,5 @@ export async function getEnhancedOpenRouterModels(): Promise Date: Thu, 12 Feb 2026 10:26:45 +0000 Subject: [PATCH 2/2] Filter slackbot_only models from org models endpoint; preserve all models in cron sync - /api/organizations/[id]/models: filter out slackbot_only by default, with ?includeSlackbotOnly=true query param to opt back in - /api/cron/sync-model-stats: explicitly include slackbot_only models since stats sync needs the complete model set - /api/organizations/[id]/defaults: already uses default filtering (no slackbot_only), which is correct for default model selection --- src/app/api/cron/sync-model-stats/route.ts | 4 +++- src/app/api/organizations/[id]/models/route.ts | 6 +++++- src/routers/organizations/organization-settings-router.ts | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/app/api/cron/sync-model-stats/route.ts b/src/app/api/cron/sync-model-stats/route.ts index 972275caf..11e3fe493 100644 --- a/src/app/api/cron/sync-model-stats/route.ts +++ b/src/app/api/cron/sync-model-stats/route.ts @@ -36,7 +36,9 @@ export async function GET(request: NextRequest) { // Fetch all models from OpenRouter (raw, unfiltered data) const openRouterResponse = await getRawOpenRouterModels(); - const enhancedOpenRouterResponse = await getEnhancedOpenRouterModels(); + const enhancedOpenRouterResponse = await getEnhancedOpenRouterModels({ + includeSlackbotOnly: true, + }); // Create a map of enhanced models for pricing lookup (includes Kilo free models with $0 pricing) const enhancedModelsMap = new Map( diff --git a/src/app/api/organizations/[id]/models/route.ts b/src/app/api/organizations/[id]/models/route.ts index 295f785a1..e6cdbd6d2 100644 --- a/src/app/api/organizations/[id]/models/route.ts +++ b/src/app/api/organizations/[id]/models/route.ts @@ -4,8 +4,12 @@ import { handleTRPCRequest } from '@/lib/trpc-route-handler'; export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const organizationId = (await params).id; + const includeSlackbotOnly = request.nextUrl.searchParams.get('includeSlackbotOnly') === 'true'; return handleTRPCRequest(request, async caller => { - return caller.organizations.settings.listAvailableModels({ organizationId }); + return caller.organizations.settings.listAvailableModels({ + organizationId, + includeSlackbotOnly, + }); }); } diff --git a/src/routers/organizations/organization-settings-router.ts b/src/routers/organizations/organization-settings-router.ts index 87a034d0a..8e5605fa5 100644 --- a/src/routers/organizations/organization-settings-router.ts +++ b/src/routers/organizations/organization-settings-router.ts @@ -144,10 +144,10 @@ const SettingsResponseSchema = z.object({ export const organizationsSettingsRouter = createTRPCRouter({ listAvailableModels: organizationMemberProcedure - .input(OrganizationIdInputSchema) + .input(OrganizationIdInputSchema.extend({ includeSlackbotOnly: z.boolean().optional() })) .output(z.custom()) .query(async ({ input }) => { - const { organizationId } = input; + const { organizationId, includeSlackbotOnly } = input; const organization = await getOrganizationById(organizationId); if (!organization) { @@ -163,7 +163,7 @@ export const organizationsSettingsRouter = createTRPCRouter({ allowedModels = organization.settings.model_allow_list; } - const responseData = await getEnhancedOpenRouterModels(); + const responseData = await getEnhancedOpenRouterModels({ includeSlackbotOnly }); let filteredModels = responseData.data; if (allowedModels) {