feat(chat-workflow): POST /api/chat/workflow route stub (PR 2 of 5)
Summary
Implements the route stub for the new POST /api/chat/workflow endpoint documented in recoupable/docs#221. Validates the full request contract, performs all auth + ownership + sandbox checks, and returns a hardcoded UIMessage stream with an x-workflow-run-id: stub-<uuid> header — so the chat-side team can integrate against the real response shape today while the workflow itself is being ported from open-agents in PRs 3-5.
Files
app/api/chat/workflow/route.ts— thin POST shim + OPTIONS for CORS (matches existing/api/chat/route.tsconvention)lib/chat/handleChatWorkflowStream.ts— auth → validate body → session ownership → sandbox check → chat ownership → stub UIMessage streamlib/chat/validateChatWorkflowBody.ts— Zod schema matching the OpenAPIChatWorkflowRequest(messages,chatId,sessionId, optionalcontext.contextLimit)lib/chat/__tests__/handleChatWorkflowStream.test.ts— 12 testslib/chat/__tests__/validateChatWorkflowBody.test.ts— 11 tests
Status codes (match contract docs)
| Status | Trigger |
|---|---|
| 200 | UIMessage stream + x-workflow-run-id: stub-<uuid> header |
| 400 | invalid JSON / invalid body / "Sandbox not initialized" |
| 401 | validateAuthContext passthrough (no/invalid auth) |
| 403 | session not owned by the API key's account |
| 404 | session not found / chat not found / chat exists but under a different session |
| 500 | selectSessions returned null (DB error) |
409 deferred to the wire-up PR that adds compareAndSetChatActiveStreamId — no workflow to dedupe yet.
Test plan
- TDD red → green: tests written first, watched fail, then implemented
- 23/23 new tests pass (
pnpm test lib/chat/__tests__/{validateChatWorkflowBody,handleChatWorkflowStream}.test.ts) - Full suite: 2901/2901 pass (
pnpm test --run) - Lint clean (
pnpm lint) - Smoke test against a real Vercel preview: send a valid body with an existing chat/session/sandbox, confirm stream emits "Hello from /api/chat/workflow" and
x-workflow-run-idheader is present
Position in the PR series
This is PR 2 of 5 for porting open-agents' /api/chat into api as /api/chat/workflow:
- ✅ recoupable/docs#221 — contract docs (merged)
- ✅ This PR — route stub
- ⏳ Port 10 sandbox tool files +
buildAgentToolsfactory (no wiring) - ⏳ Port route-level helpers (
createChatRuntime,agentCustomInstructions,extractOrgId,compareAndSetChatActiveStreamId,touchChat,reconcileExistingActiveStream,persistLatestUserMessage) - ⏳ Wire it all together —
runAgentWorkflow+runAgentStep+ replace stub with realstart()
🤖 Generated with Claude Code
Summary by cubic
Adds a stub POST /api/chat/workflow endpoint that validates input, enforces auth and ownership, checks sandbox state, and streams a hardcoded UIMessage with an x-workflow-run-id header. Refactors validation into a dedicated validateChatWorkflow and reuses shared error helpers so clients can integrate with the final response shape now (per recoupable/docs#221).
-
New Features
- New route with CORS-friendly
OPTIONShandler. - Request validation via
validateChatWorkflow:messages,chatId,sessionId, optionalcontext.contextLimit; augments withaccountId/orgId/authToken. - Auth and data checks: API key auth, session ownership, chat belongs to session, sandbox active.
- Returns
text/event-streamUIMessage stub withx-workflow-run-id: stub-<uuid>; implements 200/400/401/403/404/500 per contract.
- New route with CORS-friendly
-
Refactors
- Moved auth + body parsing into
validateChatWorkflow; handler now delegates with a single call. - Switched to shared
errorResponseandvalidationErrorResponseutilities. - Updated tests to match the new seams.
- Removed unrelated local artifacts that were accidentally included to keep the diff scoped.
- Moved auth + body parsing into