| phase |
plan |
subsystem |
tags |
dependency_graph |
tech_stack |
key_files |
decisions |
metrics |
requirements |
| 31-puter.js-zero-config-cloud |
01 |
server |
| puter |
| proxy |
| sse |
| streaming |
| cost-tracking |
| secrets |
|
| requires |
provides |
affects |
|
|
| puterProxyService |
| puterProxyRoutes |
|
|
|
| added |
patterns |
|
|
| SSE streaming |
| AsyncGenerator |
| secretService token storage |
| conditional cost recording |
|
|
| created |
modified |
| server/src/services/puter-proxy.ts |
| server/src/routes/puter-proxy.ts |
| server/src/__tests__/31-puter-proxy.test.ts |
|
|
|
| agentId is optional in chatStream — cost recording skipped when null/undefined to avoid FK violation in cost_events |
| PUTER_DEFAULT_MODEL set to claude-3-5-haiku-20241022 matching Puter's OpenAI-compat endpoint |
| Non-blocking cost recording via .catch(() => {}) pattern — stream completes regardless of cost event persistence |
|
| duration |
completed |
tasks_completed |
files_changed |
| 4m |
2026-04-03 |
2 |
4 |
|
|
Phase 31 Plan 01: Puter Proxy Service + Routes Summary
JWT auth with Puter token stored via secretService and relayed as Bearer header to Puter's OpenAI-compatible SSE endpoint with conditional cost tracking.
What Was Built
server/src/services/puter-proxy.ts — puterProxyService(db) factory with:
storeToken(companyId, token): create-or-rotate idempotent token storage via secretService with name puter_auth_token, provider local_encrypted
resolveToken(companyId): retrieves and resolves latest secret version; throws unprocessable if not configured
chatStream(companyId, agentId, messages, model, signal): AsyncGenerator that POSTs to https://api.puter.com/puterai/openai/v1/chat/completions with stream: true and stream_options: { include_usage: true }, parses SSE lines, yields content tokens, records cost event (only when agentId is truthy)
server/src/routes/puter-proxy.ts — puterProxyRoutes(db) Express Router with:
POST /puter-proxy/token — board auth, stores token, returns { ok: true }
POST /puter-proxy/chat — board auth, SSE headers, streams tokens as data: { token: "..." }, sends data: { done: true } on completion
server/src/app.ts — import and mount api.use(puterProxyRoutes(db)) after costRoutes, inside boardMutationGuard.
server/src/__tests__/31-puter-proxy.test.ts — 10 vitest tests covering all behaviors (token create/rotate, token resolve, Puter fetch headers, SSE yielding, cost recording with agentId, cost skip without agentId, route 200/SSE/optional-agentId).
Verification
- All 10 tests passing:
npx vitest run src/__tests__/31-puter-proxy.test.ts
- All 14 acceptance criteria pass (grep checks)
- New files produce zero TypeScript errors (pre-existing plugin-sdk errors unrelated to this plan)
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Test 4 mock missing return value for createEvent
- Found during: GREEN phase test run — Test 4 passed agentId="agent-1" triggering cost recording but mockCreateEvent had no configured return (returns undefined after vi.clearAllMocks)
- Fix: Added
mockCreateEvent.mockResolvedValue({ id: "ev-1" }) to Test 4 setup
- Files modified: server/src/tests/31-puter-proxy.test.ts
- Commit:
13bc39b1
Known Stubs
None — all data paths are wired. The Puter token is resolved from secretService on every call (no stub).
Self-Check: PASSED