50 lines
2.1 KiB
TypeScript
50 lines
2.1 KiB
TypeScript
// 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<typeof createServiceRoleClient> | null = null
|
||
function service(): ReturnType<typeof createServiceRoleClient> {
|
||
serviceClient ??= createServiceRoleClient()
|
||
return serviceClient
|
||
}
|
||
|
||
function supabaseLlmQuotaStore(client: ReturnType<typeof createServiceRoleClient>): 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)
|
||
},
|
||
}))
|