Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | /** * API route for realtime voice session usage heartbeats. * * POST /api/realtime/voice-heartbeat * Body: { durationSeconds, turnCount, modelCharacters, userCharacters, * toolCallCount, endReason?, final?, model?, feature? } * * Called periodically (~15s) by the client during a voice session and * once more via sendBeacon on disconnect. Each heartbeat overwrites the * previous one for the same session (idempotent accumulator pattern). */ import { withAuth } from '@/lib/auth/withAuth' import { recordRealtimeHeartbeat } from '@/lib/ai-usage/helpers' import { AiFeature, type AiFeatureValue } from '@/lib/ai-usage/features' const VALID_FEATURES = new Set<AiFeatureValue>([ AiFeature.EUCLID_VOICE, AiFeature.NUMBER_LINE_VOICE, ]) export const POST = withAuth(async (request, { userId }) => { try { const body = await request.json() const { durationSeconds, turnCount, modelCharacters, userCharacters, toolCallCount, endReason, final, model, feature, } = body if (typeof durationSeconds !== 'number' || durationSeconds < 0) { return Response.json({ error: 'durationSeconds is required' }, { status: 400 }) } const resolvedFeature: AiFeatureValue = feature && VALID_FEATURES.has(feature) ? feature : AiFeature.NUMBER_LINE_VOICE const resolvedModel = typeof model === 'string' ? model : 'gpt-realtime-1.5' recordRealtimeHeartbeat( { durationSeconds: durationSeconds ?? 0, turnCount: turnCount ?? 0, modelCharacters: modelCharacters ?? 0, userCharacters: userCharacters ?? 0, toolCallCount: toolCallCount ?? 0, endReason: endReason ?? undefined, final: final ?? false, }, resolvedModel, { userId, feature: resolvedFeature } ) return Response.json({ ok: true }) } catch (err) { console.error('[voice-heartbeat] Error:', err) return Response.json({ error: 'Internal server error' }, { status: 500 }) } }) |