From 5a5a6a430208d95fd7bd84b4f5e50ef9a5e95834 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Feb 2026 12:03:50 +0100 Subject: [PATCH 1/9] User byok --- src/app/api/fim/completions/route.ts | 6 +- src/lib/byok/index.ts | 96 ++++++++++++------- src/lib/providers/index.ts | 29 +++--- src/lib/providers/mistral.ts | 3 + .../openrouter/inference-provider-id.ts | 8 -- src/lib/providers/vercel/index.ts | 58 +++++------ .../providers/vercel/mapModelIdToVercel.ts | 27 ++++++ 7 files changed, 135 insertions(+), 92 deletions(-) create mode 100644 src/lib/providers/vercel/mapModelIdToVercel.ts diff --git a/src/app/api/fim/completions/route.ts b/src/app/api/fim/completions/route.ts index 8c4bb824f..c468794e8 100644 --- a/src/app/api/fim/completions/route.ts +++ b/src/app/api/fim/completions/route.ts @@ -126,8 +126,8 @@ export async function POST(request: NextRequest) { const promptInfo = extractFimPromptInfo(requestBody); const userByok = organizationId - ? await getBYOKforOrganization(readDb, organizationId, 'codestral') - : await getBYOKforUser(readDb, user.id, 'codestral'); + ? await getBYOKforOrganization(readDb, organizationId, ['codestral']) + : await getBYOKforUser(readDb, user.id, ['codestral']); const usageContext: MicrodollarUsageContext = { kiloUserId: user.id, @@ -188,7 +188,7 @@ export async function POST(request: NextRequest) { method: 'POST', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${userByok?.decryptedAPIKey ?? MISTRAL_API_KEY}`, + Authorization: `Bearer ${userByok?.at(0)?.decryptedAPIKey ?? MISTRAL_API_KEY}`, }, body: JSON.stringify(bodyWithCorrectedModel), }); diff --git a/src/lib/byok/index.ts b/src/lib/byok/index.ts index 35e9fb8ca..bfb448f7b 100644 --- a/src/lib/byok/index.ts +++ b/src/lib/byok/index.ts @@ -1,28 +1,61 @@ -import type { db } from '@/lib/drizzle'; -import { byok_api_keys } from '@/db/schema'; -import { eq, and, sql } from 'drizzle-orm'; +import { readDb, type db } from '@/lib/drizzle'; +import { byok_api_keys, modelsByProvider } from '@/db/schema'; +import { eq, and, inArray } from 'drizzle-orm'; +import { desc } from 'drizzle-orm'; import { decryptApiKey } from '@/lib/byok/encryption'; import { BYOK_ENCRYPTION_KEY } from '@/lib/config.server'; -import type { UserByokProviderId } from '@/lib/providers/openrouter/inference-provider-id'; +import { + UserByokProviderIdSchema, + type UserByokProviderId, +} from '@/lib/providers/openrouter/inference-provider-id'; +import { isCodestralModel } from '@/lib/providers/mistral'; +import { unstable_cache } from 'next/cache'; +import { mapModelIdToVercel } from '@/lib/providers/vercel/mapModelIdToVercel'; export type BYOKResult = { decryptedAPIKey: string; providerId: UserByokProviderId; }; -/** - * Retrieves a decrypted BYOK API key for a user and provider. - * - * @param userId - The Kilo user ID - * @param providerId - The provider ID (case-insensitive match) - * @returns Object with decrypted API key and provider ID if found, null otherwise - */ +const getModelUserByokProviders_cached = unstable_cache( + async (modelId: string) => { + const vercelModelMetadata = ( + await readDb + .select({ vercel: modelsByProvider.vercel }) + .from(modelsByProvider) + .orderBy(desc(modelsByProvider.id)) + .limit(1) + ).at(0)?.vercel; + if (!vercelModelMetadata) { + throw new Error('No Vercel model metadata in the database'); + } + const providers = + vercelModelMetadata[mapModelIdToVercel(modelId)]?.endpoints + .map(ep => UserByokProviderIdSchema.safeParse(ep.tag)?.data) + .filter(providerId => providerId !== undefined) ?? []; + if (providers.length === 0) { + console.debug(`[getModelUserByokProviders_cached] no user byok providers for ${modelId}`); + return []; + } + return providers; + }, + undefined, + { revalidate: 300 } +); + +export async function getModelUserByokProviders(model: string): Promise { + if (isCodestralModel(model)) { + return ['codestral']; + } + return await getModelUserByokProviders_cached(model); +} + export async function getBYOKforUser( fromDb: typeof db, userId: string, - providerId: UserByokProviderId -): Promise { - const [row] = await fromDb + providerIds: UserByokProviderId[] +): Promise { + const rows = await fromDb .select({ encrypted_api_key: byok_api_keys.encrypted_api_key, provider_id: byok_api_keys.provider_id, @@ -32,33 +65,27 @@ export async function getBYOKforUser( and( eq(byok_api_keys.kilo_user_id, userId), eq(byok_api_keys.is_enabled, true), - sql`lower(${byok_api_keys.provider_id}) = lower(${providerId})` + inArray(byok_api_keys.provider_id, providerIds) ) - ); + ) + .orderBy(byok_api_keys.created_at); - if (!row) { + if (rows.length === 0) { return null; } - return { + return rows.map(row => ({ decryptedAPIKey: decryptApiKey(row.encrypted_api_key, BYOK_ENCRYPTION_KEY), providerId: row.provider_id as UserByokProviderId, - }; + })); } -/** - * Retrieves a decrypted BYOK API key for an organization and provider. - * - * @param organizationId - The organization ID - * @param providerId - The provider ID (case-insensitive match) - * @returns Object with decrypted API key and provider ID if found, null otherwise - */ export async function getBYOKforOrganization( fromDb: typeof db, organizationId: string, - providerId: UserByokProviderId -): Promise { - const [row] = await fromDb + providerIds: UserByokProviderId[] +): Promise { + const rows = await fromDb .select({ encrypted_api_key: byok_api_keys.encrypted_api_key, provider_id: byok_api_keys.provider_id, @@ -68,16 +95,17 @@ export async function getBYOKforOrganization( and( eq(byok_api_keys.organization_id, organizationId), eq(byok_api_keys.is_enabled, true), - sql`lower(${byok_api_keys.provider_id}) = lower(${providerId})` + inArray(byok_api_keys.provider_id, providerIds) ) - ); + ) + .orderBy(byok_api_keys.created_at); - if (!row) { + if (rows.length === 0) { return null; } - return { + return rows.map(row => ({ decryptedAPIKey: decryptApiKey(row.encrypted_api_key, BYOK_ENCRYPTION_KEY), providerId: row.provider_id as UserByokProviderId, - }; + })); } diff --git a/src/lib/providers/index.ts b/src/lib/providers/index.ts index b52208f98..59ac91461 100644 --- a/src/lib/providers/index.ts +++ b/src/lib/providers/index.ts @@ -20,14 +20,16 @@ import { isHaikuModel, } from '@/lib/providers/anthropic'; import { applyGigaPotatoProviderSettings } from '@/lib/providers/gigapotato'; -import { getBYOKforOrganization, getBYOKforUser, type BYOKResult } from '@/lib/byok'; +import { + getBYOKforOrganization, + getBYOKforUser, + getModelUserByokProviders, + type BYOKResult, +} from '@/lib/byok'; import type { CustomLlm } from '@/db/schema'; import { custom_llm, type User } from '@/db/schema'; import type { OpenRouterInferenceProviderId } from '@/lib/providers/openrouter/inference-provider-id'; -import { - inferUserByokProviderForModel, - OpenRouterInferenceProviderIdSchema, -} from '@/lib/providers/openrouter/inference-provider-id'; +import { OpenRouterInferenceProviderIdSchema } from '@/lib/providers/openrouter/inference-provider-id'; import { applyCoreThinkProviderSettings } from '@/lib/providers/corethink'; import { hasAttemptCompletionTool } from '@/lib/tool-calling'; import { applyGoogleModelSettings, isGeminiModel } from '@/lib/providers/google'; @@ -92,14 +94,15 @@ export async function getProvider( user: User | AnonymousUserContext, organizationId: string | undefined, taskId: string | undefined -): Promise<{ provider: Provider; userByok: BYOKResult | null; customLlm: CustomLlm | null }> { +): Promise<{ provider: Provider; userByok: BYOKResult[] | null; customLlm: CustomLlm | null }> { if (!isAnonymousContext(user)) { - const modelProvider = inferUserByokProviderForModel(requestedModel); - const userByok = !modelProvider - ? null - : organizationId - ? await getBYOKforOrganization(db, organizationId, modelProvider) - : await getBYOKforUser(db, user.id, modelProvider); + const modelProviders = await getModelUserByokProviders(requestedModel); + const userByok = + modelProviders.length === 0 + ? null + : organizationId + ? await getBYOKforOrganization(db, organizationId, modelProviders) + : await getBYOKforUser(db, user.id, modelProviders); if (userByok) { return { provider: PROVIDERS.VERCEL_AI_GATEWAY, userByok, customLlm: null }; } @@ -225,7 +228,7 @@ export function applyProviderSpecificLogic( requestedModel: string, requestToMutate: OpenRouterChatCompletionRequest, extraHeaders: Record, - userByok: BYOKResult | null + userByok: BYOKResult[] | null ) { const kiloFreeModel = kiloFreeModels.find(m => m.public_id === requestedModel); if (kiloFreeModel) { diff --git a/src/lib/providers/mistral.ts b/src/lib/providers/mistral.ts index c5ec630c4..36ea78bf6 100644 --- a/src/lib/providers/mistral.ts +++ b/src/lib/providers/mistral.ts @@ -8,6 +8,9 @@ import { export function isMistralModel(model: string) { return model.startsWith('mistralai/'); } +export function isCodestralModel(model: string) { + return model.startsWith('mistralai/'); +} export function applyMistralModelSettings(requestToMutate: OpenRouterChatCompletionRequest) { // mistral recommends this diff --git a/src/lib/providers/openrouter/inference-provider-id.ts b/src/lib/providers/openrouter/inference-provider-id.ts index e39477936..0ff353950 100644 --- a/src/lib/providers/openrouter/inference-provider-id.ts +++ b/src/lib/providers/openrouter/inference-provider-id.ts @@ -87,14 +87,6 @@ const modelPrefixToVercelInferenceProviderMapping = { 'z-ai': VercelUserByokInferenceProviderIdSchema.enum.zai, } as Record; -export function inferUserByokProviderForModel(model: string): UserByokProviderId | null { - return model.startsWith('mistralai/codestral') - ? AutocompleteUserByokProviderIdSchema.enum.codestral - : (VercelUserByokInferenceProviderIdSchema.safeParse( - inferVercelFirstPartyInferenceProviderForModel(model) - ).data ?? null); -} - export function inferVercelFirstPartyInferenceProviderForModel( model: string ): VercelInferenceProviderId | null { diff --git a/src/lib/providers/vercel/index.ts b/src/lib/providers/vercel/index.ts index 94c19d98a..d0197718e 100644 --- a/src/lib/providers/vercel/index.ts +++ b/src/lib/providers/vercel/index.ts @@ -4,7 +4,6 @@ import { isAnthropicModel } from '@/lib/providers/anthropic'; import { getGatewayErrorRate } from '@/lib/providers/gateway-error-rate'; import { AutocompleteUserByokProviderIdSchema, - inferVercelFirstPartyInferenceProviderForModel, openRouterToVercelInferenceProviderId, VercelUserByokInferenceProviderIdSchema, } from '@/lib/providers/openrouter/inference-provider-id'; @@ -14,6 +13,7 @@ import type { VercelInferenceProviderConfig, VercelProviderConfig, } from '@/lib/providers/openrouter/types'; +import { mapModelIdToVercel } from '@/lib/providers/vercel/mapModelIdToVercel'; import * as crypto from 'crypto'; // EMERGENCY SWITCH @@ -101,28 +101,13 @@ function convertProviderOptions( }; } -const vercelModelIdMapping = { - 'arcee-ai/trinity-large-preview:free': 'arcee-ai/trinity-large-preview', - 'mistralai/codestral-2508': 'mistral/codestral', - 'mistralai/devstral-2512': 'mistral/devstral-2', -} as Record; - export function applyVercelSettings( requestedModel: string, requestToMutate: OpenRouterChatCompletionRequest, extraHeaders: Record, - userByok: BYOKResult | null + userByok: BYOKResult[] | null ) { - const vercelModelId = vercelModelIdMapping[requestedModel]; - if (vercelModelId) { - requestToMutate.model = vercelModelId; - } else { - const firstPartyProvider = inferVercelFirstPartyInferenceProviderForModel(requestedModel); - const slashIndex = requestToMutate.model.indexOf('/'); - if (firstPartyProvider && slashIndex >= 0) { - requestToMutate.model = firstPartyProvider + requestToMutate.model.slice(slashIndex); - } - } + requestToMutate.model = mapModelIdToVercel(requestedModel); if (isAnthropicModel(requestedModel)) { // https://vercel.com/docs/ai-gateway/model-variants#anthropic-claude-sonnet-4:-1m-token-context-beta @@ -133,28 +118,33 @@ export function applyVercelSettings( } if (userByok) { - const provider = - userByok.providerId === AutocompleteUserByokProviderIdSchema.enum.codestral - ? VercelUserByokInferenceProviderIdSchema.enum.mistral - : userByok.providerId; - const list = new Array(); - // Z.AI Coding Plan support - if (provider === VercelUserByokInferenceProviderIdSchema.enum.zai) { - list.push({ - apiKey: userByok.decryptedAPIKey, - baseURL: 'https://api.z.ai/api/coding/paas/v4', - }); + if (userByok.length === 0) { + throw new Error('Invalid state: userByok should be null or not empty'); + } + const byokProviders: Record = {}; + for (const provider of userByok) { + const key = + provider.providerId === AutocompleteUserByokProviderIdSchema.enum.codestral + ? VercelUserByokInferenceProviderIdSchema.enum.mistral + : provider.providerId; + const list = new Array(); + if (key === VercelUserByokInferenceProviderIdSchema.enum.zai) { + // Z.AI Coding Plan support + list.push({ + apiKey: provider.decryptedAPIKey, + baseURL: 'https://api.z.ai/api/coding/paas/v4', + }); + } + list.push({ apiKey: provider.decryptedAPIKey }); + byokProviders[key] = list; } - list.push({ apiKey: userByok.decryptedAPIKey }); // this is vercel specific BYOK configuration to force vercel gateway to use the BYOK API key // for the user/org. If the key is invalid the request will faill - it will not fall back to bill our API key. requestToMutate.providerOptions = { gateway: { - only: [provider], - byok: { - [provider]: list, - }, + only: Object.keys(byokProviders), + byok: byokProviders, }, }; } else { diff --git a/src/lib/providers/vercel/mapModelIdToVercel.ts b/src/lib/providers/vercel/mapModelIdToVercel.ts new file mode 100644 index 000000000..80f4aff9d --- /dev/null +++ b/src/lib/providers/vercel/mapModelIdToVercel.ts @@ -0,0 +1,27 @@ +import { kiloFreeModels } from '@/lib/models'; +import { inferVercelFirstPartyInferenceProviderForModel } from '@/lib/providers/openrouter/inference-provider-id'; + +const vercelModelIdMapping = { + 'arcee-ai/trinity-large-preview:free': 'arcee-ai/trinity-large-preview', + 'mistralai/codestral-2508': 'mistral/codestral', + 'mistralai/devstral-2512': 'mistral/devstral-2', +} as Record; + +export function mapModelIdToVercel(modelId: string) { + const hardcodedVercelId = vercelModelIdMapping[modelId]; + if (hardcodedVercelId) { + return hardcodedVercelId; + } + + const slashIndex = modelId.indexOf('/'); + if (slashIndex < 0) { + return modelId; + } + + const internalId = + kiloFreeModels.find(m => m.public_id === modelId && m.is_enabled && m.gateway === 'openrouter') + ?.internal_id ?? modelId; + + const firstPartyProvider = inferVercelFirstPartyInferenceProviderForModel(internalId); + return firstPartyProvider ? firstPartyProvider + internalId.slice(slashIndex) : internalId; +} From 52f82fd37a1dd77e8c246fcd4fbdea3480aa9e58 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Feb 2026 13:38:58 +0100 Subject: [PATCH 2/9] Fix --- src/lib/providers/mistral.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/providers/mistral.ts b/src/lib/providers/mistral.ts index 36ea78bf6..69e00e0e8 100644 --- a/src/lib/providers/mistral.ts +++ b/src/lib/providers/mistral.ts @@ -9,7 +9,7 @@ export function isMistralModel(model: string) { return model.startsWith('mistralai/'); } export function isCodestralModel(model: string) { - return model.startsWith('mistralai/'); + return model.startsWith('mistralai/codestral'); } export function applyMistralModelSettings(requestToMutate: OpenRouterChatCompletionRequest) { From f58840ed909e241177c81db51baf8e63eb9bcb17 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Feb 2026 13:39:53 +0100 Subject: [PATCH 3/9] Avoid as operator I guess --- src/lib/providers/vercel/mapModelIdToVercel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/providers/vercel/mapModelIdToVercel.ts b/src/lib/providers/vercel/mapModelIdToVercel.ts index 80f4aff9d..9428aa20c 100644 --- a/src/lib/providers/vercel/mapModelIdToVercel.ts +++ b/src/lib/providers/vercel/mapModelIdToVercel.ts @@ -1,11 +1,11 @@ import { kiloFreeModels } from '@/lib/models'; import { inferVercelFirstPartyInferenceProviderForModel } from '@/lib/providers/openrouter/inference-provider-id'; -const vercelModelIdMapping = { +const vercelModelIdMapping: Record = { 'arcee-ai/trinity-large-preview:free': 'arcee-ai/trinity-large-preview', 'mistralai/codestral-2508': 'mistral/codestral', 'mistralai/devstral-2512': 'mistral/devstral-2', -} as Record; +}; export function mapModelIdToVercel(modelId: string) { const hardcodedVercelId = vercelModelIdMapping[modelId]; From 8807de2e51de615651ae1452bb777a2a74c333df Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Feb 2026 13:41:58 +0100 Subject: [PATCH 4/9] Remove unnecessary chaining --- src/lib/byok/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/byok/index.ts b/src/lib/byok/index.ts index bfb448f7b..eca594ac1 100644 --- a/src/lib/byok/index.ts +++ b/src/lib/byok/index.ts @@ -31,7 +31,7 @@ const getModelUserByokProviders_cached = unstable_cache( } const providers = vercelModelMetadata[mapModelIdToVercel(modelId)]?.endpoints - .map(ep => UserByokProviderIdSchema.safeParse(ep.tag)?.data) + .map(ep => UserByokProviderIdSchema.safeParse(ep.tag).data) .filter(providerId => providerId !== undefined) ?? []; if (providers.length === 0) { console.debug(`[getModelUserByokProviders_cached] no user byok providers for ${modelId}`); From ba4baa8f7f2fa850f9ea1cd2abba77b6241d82da Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Feb 2026 13:46:19 +0100 Subject: [PATCH 5/9] Use spread operator --- src/lib/providers/vercel/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/providers/vercel/index.ts b/src/lib/providers/vercel/index.ts index d0197718e..ab1841f14 100644 --- a/src/lib/providers/vercel/index.ts +++ b/src/lib/providers/vercel/index.ts @@ -136,7 +136,7 @@ export function applyVercelSettings( }); } list.push({ apiKey: provider.decryptedAPIKey }); - byokProviders[key] = list; + byokProviders[key] = [...(byokProviders[key] ?? []), ...list]; } // this is vercel specific BYOK configuration to force vercel gateway to use the BYOK API key From b708aa6de5bffa9d7210acdb8440a68bddf0a395 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Feb 2026 13:50:01 +0100 Subject: [PATCH 6/9] Fix --- src/lib/providers/vercel/mapModelIdToVercel.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/providers/vercel/mapModelIdToVercel.ts b/src/lib/providers/vercel/mapModelIdToVercel.ts index 9428aa20c..6d109262d 100644 --- a/src/lib/providers/vercel/mapModelIdToVercel.ts +++ b/src/lib/providers/vercel/mapModelIdToVercel.ts @@ -13,15 +13,15 @@ export function mapModelIdToVercel(modelId: string) { return hardcodedVercelId; } - const slashIndex = modelId.indexOf('/'); - if (slashIndex < 0) { - return modelId; - } - const internalId = kiloFreeModels.find(m => m.public_id === modelId && m.is_enabled && m.gateway === 'openrouter') ?.internal_id ?? modelId; + const slashIndex = internalId.indexOf('/'); + if (slashIndex < 0) { + return internalId; + } + const firstPartyProvider = inferVercelFirstPartyInferenceProviderForModel(internalId); return firstPartyProvider ? firstPartyProvider + internalId.slice(slashIndex) : internalId; } From 9ba4fe4096ed91c4729a41a3f574c1038264396c Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Feb 2026 13:57:47 +0100 Subject: [PATCH 7/9] Extra logging, simplify --- src/lib/byok/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/byok/index.ts b/src/lib/byok/index.ts index eca594ac1..295af063b 100644 --- a/src/lib/byok/index.ts +++ b/src/lib/byok/index.ts @@ -37,6 +37,10 @@ const getModelUserByokProviders_cached = unstable_cache( console.debug(`[getModelUserByokProviders_cached] no user byok providers for ${modelId}`); return []; } + console.debug( + `[getModelUserByokProviders_cached] found user byok providers for ${modelId}`, + providers + ); return providers; }, undefined, @@ -44,10 +48,7 @@ const getModelUserByokProviders_cached = unstable_cache( ); export async function getModelUserByokProviders(model: string): Promise { - if (isCodestralModel(model)) { - return ['codestral']; - } - return await getModelUserByokProviders_cached(model); + return isCodestralModel(model) ? ['codestral'] : await getModelUserByokProviders_cached(model); } export async function getBYOKforUser( From c7888ea33b58e385b7999bdb85baf9cc66995d13 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Feb 2026 14:03:12 +0100 Subject: [PATCH 8/9] This is the better enum to use --- src/lib/byok/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/byok/index.ts b/src/lib/byok/index.ts index 295af063b..908931df6 100644 --- a/src/lib/byok/index.ts +++ b/src/lib/byok/index.ts @@ -5,7 +5,7 @@ import { desc } from 'drizzle-orm'; import { decryptApiKey } from '@/lib/byok/encryption'; import { BYOK_ENCRYPTION_KEY } from '@/lib/config.server'; import { - UserByokProviderIdSchema, + VercelUserByokInferenceProviderIdSchema, type UserByokProviderId, } from '@/lib/providers/openrouter/inference-provider-id'; import { isCodestralModel } from '@/lib/providers/mistral'; @@ -31,7 +31,7 @@ const getModelUserByokProviders_cached = unstable_cache( } const providers = vercelModelMetadata[mapModelIdToVercel(modelId)]?.endpoints - .map(ep => UserByokProviderIdSchema.safeParse(ep.tag).data) + .map(ep => VercelUserByokInferenceProviderIdSchema.safeParse(ep.tag).data) .filter(providerId => providerId !== undefined) ?? []; if (providers.length === 0) { console.debug(`[getModelUserByokProviders_cached] no user byok providers for ${modelId}`); From fbaa0a6bd9fec0485c47fe7851b02b5310b68585 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 25 Feb 2026 14:10:33 +0100 Subject: [PATCH 9/9] Bot review comments --- src/lib/byok/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/byok/index.ts b/src/lib/byok/index.ts index 908931df6..25cadb35c 100644 --- a/src/lib/byok/index.ts +++ b/src/lib/byok/index.ts @@ -5,6 +5,7 @@ import { desc } from 'drizzle-orm'; import { decryptApiKey } from '@/lib/byok/encryption'; import { BYOK_ENCRYPTION_KEY } from '@/lib/config.server'; import { + UserByokProviderIdSchema, VercelUserByokInferenceProviderIdSchema, type UserByokProviderId, } from '@/lib/providers/openrouter/inference-provider-id'; @@ -27,7 +28,8 @@ const getModelUserByokProviders_cached = unstable_cache( .limit(1) ).at(0)?.vercel; if (!vercelModelMetadata) { - throw new Error('No Vercel model metadata in the database'); + console.error('[getModelUserByokProviders_cached] no Vercel model metadata in the database'); + return []; } const providers = vercelModelMetadata[mapModelIdToVercel(modelId)]?.endpoints @@ -77,7 +79,7 @@ export async function getBYOKforUser( return rows.map(row => ({ decryptedAPIKey: decryptApiKey(row.encrypted_api_key, BYOK_ENCRYPTION_KEY), - providerId: row.provider_id as UserByokProviderId, + providerId: UserByokProviderIdSchema.parse(row.provider_id), })); } @@ -107,6 +109,6 @@ export async function getBYOKforOrganization( return rows.map(row => ({ decryptedAPIKey: decryptApiKey(row.encrypted_api_key, BYOK_ENCRYPTION_KEY), - providerId: row.provider_id as UserByokProviderId, + providerId: UserByokProviderIdSchema.parse(row.provider_id), })); }