feat(credits): charge credits per chat turn (atomic wallet debit + audit)
Summary
Closes the silent revenue-loss gap tracked in #605 — every successful chat workflow turn on `/api/chat/workflow` now debits the account's wallet and records a `usage_events` audit row, atomically.
Built on the `deduct_credits_with_audit` Postgres function shipped in recoupable/database#26.
End-to-end flow
- `runAgentStep` already captures `responseMessage.metadata` (`totalMessageCost`, `totalMessageUsage`) via `onFinish` — same number the chat UI shows next to the assistant response.
- `runAgentWorkflow` now calls `recordChatUsage(accountId, modelId, responseMessage)` after `persistAssistantMessage`.
- `recordChatUsage` → `computeCreditsDeductedCents` (gateway cost → token estimate → 1c floor) → `deductCreditsWithAudit`.
- `deduct_credits_with_audit` runs the wallet `UPDATE` + audit `INSERT` in one PL/pgSQL transaction. Either both land or neither does — matches open-agents' `db.transaction(...)` atomicity guarantee.
`accountId` is threaded through `RunAgentWorkflowInput` from `validateChatWorkflow` (auth-derived — never trusted from the request body).
New files (all TDD: red → minimal impl → green)
| File | Purpose | Tests |
|---|---|---|
| `lib/credits/AvailableModelCost.ts` | Cost-catalog type | type-only |
| `lib/credits/estimateModelUsageCost.ts` | Token-based USD estimator with 200k+ tier swap | 13 |
| `lib/credits/computeCreditsDeductedCents.ts` | Gateway cost → estimate → 1c floor | 14 |
| `lib/supabase/credits_usage/deductCreditsWithAudit.ts` | Thin `supabase.rpc` wrapper, fire-and-forget contract | 4 |
| `app/lib/workflows/recordChatUsage.ts` | `"use step"` that ties it all together | 8 |
Updates
- `app/lib/workflows/runAgentWorkflow.ts` — `accountId` field on `RunAgentWorkflowInput` + `recordChatUsage` call after `persistAssistantMessage`
- `lib/chat/handleChatWorkflowStream.ts` — passes `validated.accountId` into `start(runAgentWorkflow, ...)`
- `app/lib/workflows/tests/runAgentWorkflow.test.ts` — 3 new tests (records on success, skips when no responseMessage, skips when runAgentStep throws)
Total: 42 new unit tests. Suite: 3205 → 3220 passing.
Test plan
- 42 new unit tests pass
- Full suite passes (3220/3220)
- Lint / format clean
- Manual preview verification — trigger a chat workflow on the preview and confirm:
- `GET /api/accounts/{accountId}/credits` shows `remaining_credits` decrement
- `/api/admins/credits/events` shows a fresh `usage_events` row with `source='api'`, `agent_type='main'`, the model id, token counts, and `credits_deducted_cents` matching the chat UI's displayed cost
- Runtime logs show `[recordChatUsage] success { accountId, modelId, cents }`
Why one PR instead of "step 2/6"
The previous slice (#611) was lib code only with no integration point — preview deployments couldn't exercise the new functions. Folded back together so this PR ships a complete user-observable change.
🤖 Generated with Claude Code
Summary by cubic
Charges credits for each successful chat turn on /api/chat/workflow, debiting the wallet and writing a usage_events audit row atomically. Billing is consolidated into handleChatCredits and prefers gateway-reported cost to match the UI, closing the free-turn gap tracked in #605.
-
Refactors
- Integrated
handleChatCreditsat the end ofrunAgentWorkflowafterpersistAssistantMessage; threadedaccountIdfromvalidateChatWorkflowthroughhandleChatWorkflowStreamandRunAgentWorkflowInput. getCreditUsageacceptsgatewayCostUsdand short-circuits when positive; otherwise falls back to token pricing with a 1c floor.recordCreditDeductionnow callsdeduct_credits_with_auditvialib/supabase/credits_usage/deductCreditsWithAudit.tsfor atomic debit + audit; addssourcelabeling (apifor workflow, defaultwebelsewhere).- Workflow cleanup:
runAgentWorkflowreadsAgentMessageMetadataand uses aZERO_USAGEfallback when metadata is missing (no behavior change).
- Integrated
-
Bug Fixes
- Flattened
DeductCreditsWithAuditResultto avoid Next.js 16 narrowing issues. - Regenerated
types/database.types.tsto includededuct_credits_with_auditand castparams.eventtoJsonin the RPC wrapper. - Marked
recordCreditDeductionwith'use step'sonanoidruns in the workflow step runtime.
- Flattened
Summary by CodeRabbit
-
New Features
- Added API source labeling to distinguish usage charges between API and web interface activity.
- Introduced support for gateway-reported per-turn costs in credit calculations.
-
Bug Fixes
- Improved billing reliability by implementing atomic transaction processing for credit deductions and audit logging, ensuring consistency between wallet updates and usage records.