- 31-01-SUMMARY.md: documents puterProxyService, routes, and 10 tests - STATE.md: advance plan to 2, record metrics, session stop - ROADMAP.md: update phase 31 progress (2/4 plans complete) - REQUIREMENTS.md: mark CLOUD-01, CLOUD-02 complete
71 lines
3.4 KiB
Markdown
71 lines
3.4 KiB
Markdown
---
|
|
phase: 31-puter.js-zero-config-cloud
|
|
plan: "01"
|
|
subsystem: server
|
|
tags: [puter, proxy, sse, streaming, cost-tracking, secrets]
|
|
dependency_graph:
|
|
requires: []
|
|
provides: [puterProxyService, puterProxyRoutes]
|
|
affects: [server/src/app.ts]
|
|
tech_stack:
|
|
added: []
|
|
patterns: [SSE streaming, AsyncGenerator, secretService token storage, conditional cost recording]
|
|
key_files:
|
|
created:
|
|
- server/src/services/puter-proxy.ts
|
|
- server/src/routes/puter-proxy.ts
|
|
- server/src/__tests__/31-puter-proxy.test.ts
|
|
modified:
|
|
- server/src/app.ts
|
|
decisions:
|
|
- "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"
|
|
metrics:
|
|
duration: 4m
|
|
completed: 2026-04-03
|
|
tasks_completed: 2
|
|
files_changed: 4
|
|
requirements: [CLOUD-01, CLOUD-02]
|
|
---
|
|
|
|
# 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
|