From 19aedc4cc580b89d09680ed3ee86b275ee566888 Mon Sep 17 00:00:00 2001 From: Remon Oldenbeuving Date: Wed, 12 Aug 2026 17:31:57 +0200 Subject: [PATCH] refactor(web): remove Snowflake-backed public leaderboard endpoints The Snowflake connection these endpoints depend on is gone, so all three public leaderboard reports return 503/502 rather than data. Removes the endpoints and the support code that only existed for them: - api/public/leaderboard-model-usage - api/public/leaderboard-model-provider-usage - api/public/leaderboard-provider-race - lib/public-snowflake-report.ts (imported only by the three routes) - lib/public-inference-provider.ts (imported only by the model-provider route) - the three public-api:leaderboard-* keys in lib/redis-keys.ts lib/snowflake.ts stays, since routers/usage-analytics-router.ts is still a consumer. api/public/oss-sponsors is Postgres-backed and unaffected, as is the separate Snowflake client in services/kiloclaw-billing. Orphaned Redis entries expire on their own within the existing 1h TTL, so no cleanup is required. --- .../leaderboard-model-provider-usage/route.ts | 174 ------------------ .../public/leaderboard-model-usage/route.ts | 72 -------- .../leaderboard-provider-race/route.test.ts | 109 ----------- .../public/leaderboard-provider-race/route.ts | 91 --------- apps/web/src/lib/public-inference-provider.ts | 71 ------- apps/web/src/lib/public-snowflake-report.ts | 138 -------------- apps/web/src/lib/redis-keys.ts | 6 - 7 files changed, 661 deletions(-) delete mode 100644 apps/web/src/app/api/public/leaderboard-model-provider-usage/route.ts delete mode 100644 apps/web/src/app/api/public/leaderboard-model-usage/route.ts delete mode 100644 apps/web/src/app/api/public/leaderboard-provider-race/route.test.ts delete mode 100644 apps/web/src/app/api/public/leaderboard-provider-race/route.ts delete mode 100644 apps/web/src/lib/public-inference-provider.ts delete mode 100644 apps/web/src/lib/public-snowflake-report.ts diff --git a/apps/web/src/app/api/public/leaderboard-model-provider-usage/route.ts b/apps/web/src/app/api/public/leaderboard-model-provider-usage/route.ts deleted file mode 100644 index bcc7248e18..0000000000 --- a/apps/web/src/app/api/public/leaderboard-model-provider-usage/route.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { z } from 'zod'; - -import { normalizePublicInferenceProvider } from '@/lib/public-inference-provider'; -import { - createPublicSnowflakeReport, - publicSnowflakeReportOptions, -} from '@/lib/public-snowflake-report'; -import { LEADERBOARD_MODEL_PROVIDER_USAGE_REDIS_KEY } from '@/lib/redis-keys'; - -const MINIMUM_TOKENS = 10_000_000; -const MAXIMUM_ERROR_RATE = 0.5; - -const LEADERBOARD_MODEL_PROVIDER_USAGE_QUERY = ` -select - mu.requested_model as "model" - , mu.inference_provider as "provider" - , sum(mu.total_cost_microdollars) as "sum_cost" - , sum(mu.total_input_tokens) + sum(mu.total_output_tokens) as "sum_tokens" - , sum(mu.total_input_tokens) as "sum_input_tokens" - , sum(mu.total_cache_hit_tokens) as "sum_cache_hit_tokens" - , sum(mu.request_count) as "sum_request_count" - , sum(mu.error_count) as "sum_error_count" -from kilo_dw.dbt_prod.microdollar_usage_daily as mu -where - mu.usage_date >= dateadd(week, -1, current_date()) - and mu.usage_date < current_date() - and mu.provider not in ('custom', 'direct-byok') - and mu.total_output_tokens > 0 - and mu.is_user_byok = false - and mu.requested_model not ilike '%mercury-edit%' -group by 1, 2 -order by 4 desc; -`; - -const modelProviderUsageSchema = z.array( - z.object({ - model: z.string().min(1), - provider: z.string().min(1), - sumTokens: z.number(), - costPerRequest: z.number(), - costPerMillionTokens: z.number(), - cacheRatio: z.number(), - errorRate: z.number(), - percentageOfModel: z.number(), - }) -); - -type ModelProviderUsage = z.infer[number]; - -type AggregatedUsage = { - model: string; - provider: string; - sumCost: number; - sumTokens: number; - sumInputTokens: number; - sumCacheHitTokens: number; - sumRequestCount: number; - sumErrorCount: number; -}; - -function normalizeModel(model: string): string { - const withoutTrailingSlashes = model.replace(/\/+$/, ''); - const slashIndex = withoutTrailingSlashes.lastIndexOf('/'); - const withoutProvider = withoutTrailingSlashes.startsWith('openrouter/') - ? withoutTrailingSlashes - : slashIndex >= 0 - ? withoutTrailingSlashes.slice(slashIndex + 1) - : withoutTrailingSlashes; - const colonIndex = withoutProvider.indexOf(':'); - - if (colonIndex < 0 || withoutProvider.slice(colonIndex) === ':free') { - return withoutProvider; - } - - return withoutProvider.slice(0, colonIndex); -} - -function ratio(numerator: number, denominator: number): number { - return denominator === 0 ? 0 : numerator / denominator; -} - -function parseAndAggregateUsage(rows: string[][]): ModelProviderUsage[] { - const usageByModelAndProvider = new Map(); - - for (const row of rows) { - const [rawModel, rawProvider, ...rawAggregates] = row; - const [sumCost, sumTokens, sumInputTokens, sumCacheHitTokens, sumRequestCount, sumErrorCount] = - rawAggregates.map(Number); - const model = normalizeModel(rawModel?.trim() || ''); - const provider = normalizePublicInferenceProvider(rawProvider?.trim() || 'unknown'); - - if ( - !model || - ![ - sumCost, - sumTokens, - sumInputTokens, - sumCacheHitTokens, - sumRequestCount, - sumErrorCount, - ].every(Number.isFinite) - ) { - throw new Error('Snowflake returned an invalid leaderboard model provider usage row'); - } - - if (provider.key === 'other') { - continue; - } - - const aggregationKey = `${model}\0${provider.key}`; - const existing = usageByModelAndProvider.get(aggregationKey); - - if (existing) { - existing.sumCost += sumCost; - existing.sumTokens += sumTokens; - existing.sumInputTokens += sumInputTokens; - existing.sumCacheHitTokens += sumCacheHitTokens; - existing.sumRequestCount += sumRequestCount; - existing.sumErrorCount += sumErrorCount; - } else { - usageByModelAndProvider.set(aggregationKey, { - model, - provider: provider.name, - sumCost, - sumTokens, - sumInputTokens, - sumCacheHitTokens, - sumRequestCount, - sumErrorCount, - }); - } - } - - const aggregatedUsage = [...usageByModelAndProvider.values()]; - const totalTokensByModel = new Map(); - - for (const usage of aggregatedUsage) { - totalTokensByModel.set( - usage.model, - (totalTokensByModel.get(usage.model) ?? 0) + usage.sumTokens - ); - } - - return aggregatedUsage - .filter(usage => usage.sumTokens >= MINIMUM_TOKENS) - .sort( - (left, right) => - right.sumTokens - left.sumTokens || - left.model.localeCompare(right.model) || - left.provider.localeCompare(right.provider) - ) - .map(usage => ({ - model: usage.model, - provider: usage.provider, - sumTokens: usage.sumTokens, - costPerRequest: ratio(usage.sumCost, usage.sumRequestCount - usage.sumErrorCount) / 1e6, - costPerMillionTokens: ratio(usage.sumCost, usage.sumTokens), - cacheRatio: ratio(usage.sumCacheHitTokens, usage.sumInputTokens), - errorRate: ratio(usage.sumErrorCount, usage.sumRequestCount), - percentageOfModel: - ratio(usage.sumTokens, totalTokensByModel.get(usage.model) ?? 0) * 100, - })).filter(usage => usage.errorRate < MAXIMUM_ERROR_RATE); -} - -export const GET = createPublicSnowflakeReport({ - cacheKey: LEADERBOARD_MODEL_PROVIDER_USAGE_REDIS_KEY, - errorMessage: 'Failed to fetch leaderboard model provider usage', - parseRows: parseAndAggregateUsage, - query: LEADERBOARD_MODEL_PROVIDER_USAGE_QUERY, - schema: modelProviderUsageSchema, - source: 'public-leaderboard-model-provider-usage-api', -}); - -export const OPTIONS = publicSnowflakeReportOptions; diff --git a/apps/web/src/app/api/public/leaderboard-model-usage/route.ts b/apps/web/src/app/api/public/leaderboard-model-usage/route.ts deleted file mode 100644 index 5c438a1eba..0000000000 --- a/apps/web/src/app/api/public/leaderboard-model-usage/route.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { z } from 'zod'; - -import { - createPublicSnowflakeReport, - publicSnowflakeReportOptions, -} from '@/lib/public-snowflake-report'; -import { LEADERBOARD_MODEL_USAGE_REDIS_KEY } from '@/lib/redis-keys'; - -const LEADERBOARD_MODEL_USAGE_QUERY = ` -select - to_char(mu.usage_date, 'YYYY-MM-DD') as usage_date - , coalesce(mu.requested_model, mu.model) as "model" - , case - when mu.feature ilike '%claw%' then 'kiloclaw' - when mu.mode ilike '%code%' then 'code' - when mu.mode ilike '%review%' then 'review' - when mu.mode ilike '%plan%' then 'plan' - when mu.mode ilike '%ask%' then 'ask' - when mu.mode ilike '%debug%' then 'debug' - else null - end as mode - , sum(coalesce(mu.total_input_tokens, 0)) + sum(coalesce(mu.total_output_tokens, 0)) as "tokens" -from kilo_dw.dbt_prod.microdollar_usage_daily as mu -where - mu.usage_date >= dateadd(week, -1, current_date()) - and mu.usage_date < current_date() - and mu.total_input_tokens > 0 - and mu.provider != 'custom' -group by 1, 2, 3 -order by 1 desc, 4 desc; -`; - -const modelUsageSchema = z.array( - z.object({ - usageDate: z.string(), - model: z.string().min(1), - mode: z.enum(['kiloclaw', 'code', 'review', 'plan', 'ask', 'debug']).nullable(), - tokens: z.number(), - }) -); - -type ModelUsage = z.infer[number]; - -function parseModelUsage(rows: string[][]): ModelUsage[] { - return rows.map(row => { - const [usageDate, model, modeValue, tokenValue] = row; - const tokens = Number(tokenValue); - const mode = modeValue || null; - - if (!usageDate || !model || !Number.isFinite(tokens)) { - throw new Error('Snowflake returned an invalid model usage row'); - } - - return modelUsageSchema.element.parse({ - usageDate, - model, - mode, - tokens, - }); - }); -} - -export const GET = createPublicSnowflakeReport({ - cacheKey: LEADERBOARD_MODEL_USAGE_REDIS_KEY, - errorMessage: 'Failed to fetch leaderboard model usage', - parseRows: parseModelUsage, - query: LEADERBOARD_MODEL_USAGE_QUERY, - schema: modelUsageSchema, - source: 'public-leaderboard-model-usage-api', -}); - -export const OPTIONS = publicSnowflakeReportOptions; diff --git a/apps/web/src/app/api/public/leaderboard-provider-race/route.test.ts b/apps/web/src/app/api/public/leaderboard-provider-race/route.test.ts deleted file mode 100644 index a35c35a24f..0000000000 --- a/apps/web/src/app/api/public/leaderboard-provider-race/route.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { describe, test, expect, jest } from '@jest/globals'; -import type { resolveSnowflakeConfig } from '@/lib/snowflake'; -import type { GET as RouteGet } from './route'; - -jest.mock('@sentry/nextjs', () => ({ captureException: jest.fn() })); - -jest.mock('@/lib/redis', () => ({ - redisClient: { - get: jest.fn<() => Promise>().mockResolvedValue(null), - set: jest.fn<() => Promise>().mockResolvedValue('OK'), - }, -})); - -jest.mock('@/lib/snowflake', () => ({ - resolveSnowflakeConfig: jest.fn(), - executeSnowflakeStatement: jest.fn(), -})); - -type SnowflakeConfig = ReturnType; - -const FAKE_CONFIG = { accountHost: 'test.snowflakecomputing.com' } as SnowflakeConfig; - -// The route builds a module-level in-process cache (1h TTL) at import time, so -// each test loads it in an isolated module registry to keep that cache from -// leaking between cases. -async function loadGet(opts: { - config?: SnowflakeConfig; - rows?: string[][]; - statementError?: Error; -}): Promise { - let GET!: typeof RouteGet; - await jest.isolateModulesAsync(async () => { - const snowflake = await import('@/lib/snowflake'); - const config = opts.config === undefined ? FAKE_CONFIG : opts.config; - jest.mocked(snowflake.resolveSnowflakeConfig).mockReturnValue(config); - if (opts.statementError) { - jest.mocked(snowflake.executeSnowflakeStatement).mockRejectedValue(opts.statementError); - } else { - jest.mocked(snowflake.executeSnowflakeStatement).mockResolvedValue(opts.rows ?? []); - } - ({ GET } = await import('./route')); - }); - return GET; -} - -describe('GET /api/public/leaderboard-provider-race', () => { - test('returns 503 when Snowflake is not configured', async () => { - const GET = await loadGet({ config: null }); - - const response = await GET(); - - expect(response.status).toBe(503); - expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*'); - }); - - test('returns 502 when the Snowflake query fails (e.g. is_open_weights backfill not landed)', async () => { - const GET = await loadGet({ statementError: new Error('invalid identifier IS_OPEN_WEIGHTS') }); - - const response = await GET(); - - expect(response.status).toBe(502); - }); - - test('maps explicit is_open_weights to booleans and NULL/unknown to null', async () => { - const GET = await loadGet({ - rows: [ - ['2026-07-14', 'Anthropic', 'false', '12345'], - ['2026-07-14', 'Alibaba', 'true', '6789'], - // The SQL API returns NULL cells as null at runtime despite the - // string[] row type; unmapped models land here. - ['2026-07-14', 'other', null, '999'] as unknown as string[], - // A future model_dim mapping gap must not be silently bucketed as closed. - ['2026-07-14', 'Mystery', 'unexpected', '111'], - ], - }); - - const response = await GET(); - - expect(response.status).toBe(200); - expect(await response.json()).toEqual([ - { weekStart: '2026-07-14', provider: 'Anthropic', isOpenWeights: false, tokens: 12345 }, - { weekStart: '2026-07-14', provider: 'Alibaba', isOpenWeights: true, tokens: 6789 }, - { weekStart: '2026-07-14', provider: 'other', isOpenWeights: null, tokens: 999 }, - { weekStart: '2026-07-14', provider: 'Mystery', isOpenWeights: null, tokens: 111 }, - ]); - expect(response.headers.get('Cache-Control')).toContain('public'); - expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*'); - }); - - test('returns 502 when a row is malformed', async () => { - const GET = await loadGet({ rows: [['2026-07-14', 'Anthropic', 'true', 'not-a-number']] }); - - const response = await GET(); - - expect(response.status).toBe(502); - }); -}); - -describe('OPTIONS /api/public/leaderboard-provider-race', () => { - test('returns 204 with CORS headers', async () => { - const { OPTIONS } = await import('./route'); - - const response = OPTIONS(); - - expect(response.status).toBe(204); - expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*'); - expect(response.headers.get('Access-Control-Allow-Methods')).toBe('GET'); - }); -}); diff --git a/apps/web/src/app/api/public/leaderboard-provider-race/route.ts b/apps/web/src/app/api/public/leaderboard-provider-race/route.ts deleted file mode 100644 index 89c1fdd025..0000000000 --- a/apps/web/src/app/api/public/leaderboard-provider-race/route.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { z } from 'zod'; - -import { - createPublicSnowflakeReport, - publicSnowflakeReportOptions, -} from '@/lib/public-snowflake-report'; -import { LEADERBOARD_PROVIDER_RACE_REDIS_KEY } from '@/lib/redis-keys'; - -// Weekly token volume per model lab, from a fixed start date through the most -// recent complete week. Grouped at week x model_provider_company x -// is_open_weights so a single payload drives both the per-lab "race" view and -// an open-weight vs proprietary toggle. model_provider_company and -// is_open_weights are maintained in the dbt model_dim seed (kilocode-dbt), so -// the lab mapping lives in one place rather than being re-derived here. -// The partial current week is excluded so every returned week is complete. -// Infrastructure-artifact BYOK model ids are excluded here (not in the base -// dbt model, which stays complete): URI/path ids (s3://, gs://, ...), ckpt: -// checkpoint refs, and HuggingFace class names (e.g. AtlasForCausalLM). These -// are customers pointing Kilo at their own checkpoints/endpoints - single-org -// noise that cannot be attributed to a model lab and would otherwise dominate -// the "other" bucket. -const LEADERBOARD_PROVIDER_RACE_QUERY = ` -select - to_char(date_trunc('week', ud.usage_date), 'YYYY-MM-DD') as week_start - , ud.model_provider_company as provider - , ud.is_open_weights - , sum(ud.total_tokens) as tokens -from kilo_dw.dbt_prod.usage_daily as ud -where - ud.usage_date >= '2025-07-01' - and date_trunc('week', ud.usage_date) < date_trunc('week', current_date()) - and ud.total_tokens > 0 - and not ( - ud.model like '%://%' - or ud.model ilike 'ckpt:%' - or ud.model ilike '%ForCausalLM' - ) -group by 1, 2, 3 -order by 1, 4 desc; -`; - -const providerRaceSchema = z.array( - z.object({ - weekStart: z.string().regex(/^\d{4}-\d{2}-\d{2}$/), - provider: z.string().min(1), - isOpenWeights: z.boolean().nullable(), - tokens: z.number(), - }) -); - -type ProviderRace = z.infer; - -// is_open_weights is maintained in the dbt model_dim seed and is NULL for -// unmapped models (the ones bucketed as provider = 'other'). Only the explicit -// 'true'/'false' strings map to a boolean; anything else (NULL from the SQL API, -// or a future mapping gap) becomes null so the client can distinguish "unknown" -// from a confirmed closed-weight lab instead of silently bucketing gaps as closed. -function parseOpenWeights(raw: string): boolean | null { - if (raw === 'true') return true; - if (raw === 'false') return false; - return null; -} - -function parseProviderRace(rows: string[][]): ProviderRace { - return rows.map(row => { - const [weekStart, provider, rawOpenWeights, rawTokens] = row; - const tokens = Number(rawTokens); - - if (!weekStart || !provider || !Number.isFinite(tokens)) { - throw new Error('Snowflake returned an invalid provider race row'); - } - - return providerRaceSchema.element.parse({ - weekStart, - provider, - isOpenWeights: parseOpenWeights(rawOpenWeights), - tokens, - }); - }); -} - -export const GET = createPublicSnowflakeReport({ - cacheKey: LEADERBOARD_PROVIDER_RACE_REDIS_KEY, - errorMessage: 'Failed to fetch leaderboard provider race', - parseRows: parseProviderRace, - query: LEADERBOARD_PROVIDER_RACE_QUERY, - schema: providerRaceSchema, - source: 'public-leaderboard-provider-race-api', -}); - -export const OPTIONS = publicSnowflakeReportOptions; diff --git a/apps/web/src/lib/public-inference-provider.ts b/apps/web/src/lib/public-inference-provider.ts deleted file mode 100644 index 1f35ef6ed9..0000000000 --- a/apps/web/src/lib/public-inference-provider.ts +++ /dev/null @@ -1,71 +0,0 @@ -const PROVIDER_ALIASES: Record = { - amazonbedrock: 'bedrock', - custom: 'other', - directbyok: 'other', - googleaistudio: 'google', - martian: 'stealth', - seed: 'bytedance', - togetherai: 'together', - unknown: 'other', - vertex: 'google', - vertexanthropic: 'google', -}; - -const PROVIDER_NAMES: Record = { - ai21: 'AI21', - aionlabs: 'Aion Labs', - akashml: 'Akash ML', - arceeai: 'Arcee AI', - atlascloud: 'Atlas Cloud', - bedrock: 'Amazon Bedrock', - bytedance: 'ByteDance', - dekallm: 'DekaLLM', - deepinfra: 'DeepInfra', - deepseek: 'DeepSeek', - digitalocean: 'DigitalOcean', - fireworks: 'Fireworks AI', - friendli: 'Friendli AI', - gmicloud: 'GMI Cloud', - google: 'Google', - inception: 'Inception', - inceptron: 'Inceptron', - ionet: 'IO.net', - minimax: 'MiniMax', - modelrun: 'ModelRun', - moonshotai: 'Moonshot AI', - nexagi: 'Nex AGI', - nextbit: 'NextBit', - novita: 'Novita AI', - nvidia: 'NVIDIA', - openai: 'OpenAI', - openinference: 'OpenInference', - other: 'Other', - sambanova: 'SambaNova', - siliconflow: 'SiliconFlow', - stealth: 'Stealth', - stepfun: 'StepFun', - streamlake: 'StreamLake', - together: 'Together AI', - unknown: 'Unknown', - wandb: 'Weights & Biases', - xai: 'SpaceXAI', - zai: 'Z.ai', -}; - -export type NormalizedPublicInferenceProvider = { - key: string; - name: string; -}; - -export function normalizePublicInferenceProvider( - provider: string -): NormalizedPublicInferenceProvider { - const normalizedKey = provider.toLowerCase().replace(/[^a-z0-9]/g, ''); - const key = PROVIDER_ALIASES[normalizedKey] ?? normalizedKey; - const fallbackName = provider - .trim() - .toLowerCase() - .replace(/(^|[\s_-])\w/g, character => character.toUpperCase()); - - return { key, name: PROVIDER_NAMES[key] ?? fallbackName }; -} diff --git a/apps/web/src/lib/public-snowflake-report.ts b/apps/web/src/lib/public-snowflake-report.ts deleted file mode 100644 index 6fd64be7f2..0000000000 --- a/apps/web/src/lib/public-snowflake-report.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { captureException } from '@sentry/nextjs'; -import { NextResponse } from 'next/server'; -import type { ZodType } from 'zod'; - -import { createCachedFetch } from '@/lib/cached-fetch'; -import { redisClient } from '@/lib/redis'; -import type { RedisKey } from '@/lib/redis-keys'; -import { executeSnowflakeStatement, resolveSnowflakeConfig } from '@/lib/snowflake'; - -const CORS_HEADERS = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET', - 'Access-Control-Allow-Headers': 'Content-Type', -}; - -// Single source of truth for every cache layer in front of Snowflake: the -// Vercel edge cache, the in-process cache, and the Redis cache all expire after -// one hour so the data served stays consistent across layers. -const CACHE_TTL_SECONDS = 3600; -const IN_MEMORY_CACHE_TTL_MS = CACHE_TTL_SECONDS * 1000; - -type PublicSnowflakeReportOptions = { - cacheKey: RedisKey; - errorMessage: string; - parseRows: (rows: string[][]) => Usage; - query: string; - schema: ZodType; - source: string; -}; - -function successResponse(usage: Usage): NextResponse { - return NextResponse.json(usage, { - headers: { - ...CORS_HEADERS, - 'Cache-Control': `public, s-maxage=${CACHE_TTL_SECONDS}, stale-while-revalidate=${CACHE_TTL_SECONDS}`, - }, - }); -} - -/** - * Read the report from Redis, falling back to Snowflake on a miss and - * repopulating Redis. Throws on Snowflake failure so the in-process cache keeps - * serving the last-known-good value (or `null` when nothing has been cached). - */ -async function fetchReport( - options: PublicSnowflakeReportOptions -): Promise { - try { - const cached = await redisClient.get(options.cacheKey); - if (cached !== null) { - return options.schema.parse(JSON.parse(cached)); - } - } catch (error) { - captureException(error, { - tags: { source: options.source, operation: 'redis-read' }, - }); - } - - const config = resolveSnowflakeConfig(); - if (!config) { - return null; - } - - try { - const rows = await executeSnowflakeStatement({ - config, - statement: options.query, - timeoutSeconds: 30, - }); - const usage = options.schema.parse(options.parseRows(rows)); - - try { - await redisClient.set(options.cacheKey, JSON.stringify(usage), { - ex: CACHE_TTL_SECONDS, - }); - } catch (error) { - captureException(error, { - tags: { source: options.source, operation: 'redis-write' }, - }); - } - - return usage; - } catch (error) { - captureException(error, { - tags: { source: options.source }, - }); - throw error; - } -} - -/** - * Builds a cached GET handler for a public Snowflake-backed report. - * - * Caching cascades through three layers, all expiring after one hour: the - * Vercel edge cache (`s-maxage`), an in-process `createCachedFetch` so warm - * instances avoid Redis entirely, and the Redis cache in front of Snowflake. - * The in-process cache is created once per report (module scope) and stores - * pure data, so it is safe to share across requests. - */ -export function createPublicSnowflakeReport(options: PublicSnowflakeReportOptions) { - const getCachedReport = createCachedFetch( - () => fetchReport(options), - IN_MEMORY_CACHE_TTL_MS, - null - ); - - return async function GET(): Promise { - if (!resolveSnowflakeConfig()) { - return NextResponse.json( - { error: 'Snowflake is not configured' }, - { - status: 503, - headers: { ...CORS_HEADERS, 'Cache-Control': 'no-store' }, - } - ); - } - - const usage = await getCachedReport(); - if (usage === null) { - return NextResponse.json( - { error: options.errorMessage }, - { - status: 502, - headers: { ...CORS_HEADERS, 'Cache-Control': 'no-store' }, - } - ); - } - - return successResponse(usage); - }; -} - -export function publicSnowflakeReportOptions(): NextResponse { - return new NextResponse(null, { - status: 204, - headers: CORS_HEADERS, - }); -} diff --git a/apps/web/src/lib/redis-keys.ts b/apps/web/src/lib/redis-keys.ts index f18f24f9c5..e0e7610151 100644 --- a/apps/web/src/lib/redis-keys.ts +++ b/apps/web/src/lib/redis-keys.ts @@ -51,12 +51,6 @@ export const codingPlanUsageRedisKey = (input: { `coding-plan-usage:v1:${input.userId}:${input.subscriptionId}:${input.planId}:${input.providerId}:${input.inventoryId}` ); -export const LEADERBOARD_MODEL_PROVIDER_USAGE_REDIS_KEY = redisKey( - 'public-api:leaderboard-model-provider-usage' -); -export const LEADERBOARD_MODEL_USAGE_REDIS_KEY = redisKey('public-api:leaderboard-model-usage'); -export const LEADERBOARD_PROVIDER_RACE_REDIS_KEY = redisKey('public-api:leaderboard-provider-race'); - export const REQUEST_LOGGING_OPT_INS_REDIS_KEY = redisKey('ai-gateway:request-logging-opt-ins'); export const abuseRulesClassificationRedisKey = (identityKey: string) =>