feat(web): hosted voice and attention endpoints behind account sign-in
What
The server half of the hosted free tier: a signed-in desktop no longer needs its own OPENAI_API_KEY to get voice and attention review. Two new Vercel endpoints run on the deployment's key, and BYOK stays exactly as it is — the desktop wiring lands in the next PR.
POST /api/voice/mint— mints one ephemeral Realtime client secret and returns it withcallsUrlstill pointing at OpenAI, so audio never transits this deployment; only the mint does. The session document is built server-side from the samerealtimeClientSecretRequestthe desktop mints with; the caller's whole say is a voice and a pace, each validated against the sets the build ships.POST /api/attention/review— reviews one boundedAttentionUpdateand returns the parsedAttentionDecision. Not a proxy: the instructions, decision schema,store: false, and output-token cap come from a construction now shared with the desktop evaluator, and the request body is validated field-by-field (attentionPromptUpdateFromWire) against the same bounds the local roster holds — value sets refuse rather than repair, and an overlong developer ask refuses the whole update.
Auth, quota, and the kill switch
- Both endpoints resolve the desktop's OAuth bearer token through the auth service's own
oauth2UserInfo, in process — expiry, revocation, and scope stay the OAuth provider's answers, never a second implementation. - A new
hosted_usagetable (migration included) meters each user per UTC day: 50 voice calls, 500 attention reviews. The increment is one atomic upsert taken before the upstream call, so racing requests cannot slip past the ceiling; 429 answers carry{used, limit, remaining, resetsAt}so the panel can eventually say what ran out. The ceilings are product knobs; the OpenAI project budget behind the key is the backstop. - Without
OPENAI_API_KEYin the environment both endpoints answer 503 — the same unset-key kill switch as the feedback endpoint. Upstream failures answer 502 with the status only; no upstream body, no key, and no request material is ever logged or forwarded.
Core refactor
The desktop evaluator's OpenAI Responses construction moved to sidecar-core (attention-openai.ts), and attentionUpdateInput now takes AttentionPromptUpdate — exactly the fields the prompt reads, so a hosted review sends no session identifiers or clocks. The desktop evaluator consumes the shared construction; its behavior is unchanged.
Environment (deployment)
OPENAI_API_KEY (new, enables the tier), optional LUKE_REALTIME_MODEL / LUKE_ATTENTION_MODEL overrides — the same names the desktop honours.
Testing
./scripts/check.shpasses (exit 0): repository checks, typecheck, tests, builds.- New tests: core wire-contract and shared-construction tests (8), plus web tests for bearer resolution, quota arithmetic and racing-ceiling behavior, and both handlers' gate order, upstream failure paths, and key-never-leaks assertions (17).
- Portable-only change: no macOS or UI surface touched, so no
verify.sh/ visual evidence run.
🤖 Generated with Claude Code
Automated visual evidence
Download the deterministic macOS evidence · workflow run
- Commit:
3361ee54a0f603fd3b0d6f607df25e08732d3ef7 - Scenario:
smoke - Physical-notch check: not performed by CI