// server/supabase/functions/llm-proxy/index.ts // Anthropic Claude Messages API 프록시 — composition root. // Phase 3.2: 모델별 쿼터 (Haiku/Sonnet/Opus × Free/Pro/Pro+) // 요청: application/json { messages, system?, max_tokens?, model? } // 응답: JSON (non-stream) 또는 SSE (stream=true) // 유스케이스와 쿼터/기한 규칙은 handler.ts, 공급자 기한은 provider-deadline.ts 에 있다. import { requireUser } from '../_shared/auth.ts' import { createServiceRoleClient, finalizeLlmQuota, readSubscriptionTier, reserveLlmQuota, } from '../_shared/quota.ts' import { GenerationReceiptError, parseGenerationReceiptId } from '../_shared/generation-receipt.ts' import { readProviderKey } from '../_shared/provider-key.ts' import { createLlmProxyHandler, type LlmQuotaStore } from './handler.ts' let serviceClient: ReturnType | null = null function service(): ReturnType { serviceClient ??= createServiceRoleClient() return serviceClient } function supabaseLlmQuotaStore(client: ReturnType): LlmQuotaStore { return { readTier: (userId) => readSubscriptionTier(userId, client), reserve: (userId, reservationId, feature, baseLimit, period) => reserveLlmQuota(userId, reservationId, feature, baseLimit, period, client), finalize: (reservationId, succeeded) => finalizeLlmQuota(reservationId, succeeded, client), } } Deno.serve(createLlmProxyHandler({ authenticate: requireUser, providerKey: () => readProviderKey('ANTHROPIC_API_KEY'), quotaStore: () => supabaseLlmQuotaStore(service()), issueGenerationReceipt: async (userId, purpose, model) => { const { data, error } = await service().rpc('issue_content_generation_receipt_v1', { p_actor_id: userId, p_purpose: purpose, p_model: model, }) if (error) { console.error('Generation receipt issuance failed', { code: error.code ?? 'unknown' }) throw new GenerationReceiptError('generation_receipt_unavailable') } return parseGenerationReceiptId(data) }, }))