Task 04 — Handler withAccount wrapper
Depends on: 03
Unblocks: 07, 08, 09, 10
Goal
Replace the inline acquire/release placeholder from task 03 with a single
withAccount helper that handles retry, cooldown, abort, and the
x-internal-pricing-sync exemption.
Scope
New file src/lib/with-account.ts:
export async function withAccount<T>(
c: Context,
fn: (account: Account) => Promise<T>,
): Promise<T> {
const isInternal = c.req.header('x-internal-pricing-sync') === '1'
const maxRetries = Math.min(state.pool.size(), 3)
let lastErr: unknown
for (let attempt = 0; attempt < maxRetries; attempt++) {
const account = await state.pool.acquire()
try {
const out = await fn(account)
account.consecutiveFailures = 0
return out
} catch (e) {
lastErr = e
if (isClientError(e)) throw e // 4xx (non-401) — no retry
if (isAuthError(e)) triggerRefresh(account) // 401 — refresh, then retry
else state.pool.markCooldown(account, 30_000) // 5xx / network
} finally {
state.pool.release(account)
}
}
throw lastErr
}
Update each handler to:
return withAccount(c, async (account) => {
// ...existing logic with `account` threaded into service call...
})
Streaming handlers must not retry once the SSE response has begun flushing.
Either:
- Detect "headers already sent" and rethrow without rotating, OR
- Wrap retry only around the
fetch() upstream call, and once events start
flowing, abort retry.
Definition of Done
Task 04 — Handler
withAccountwrapperDepends on: 03
Unblocks: 07, 08, 09, 10
Goal
Replace the inline
acquire/releaseplaceholder from task 03 with a singlewithAccounthelper that handles retry, cooldown, abort, and thex-internal-pricing-syncexemption.Scope
New file
src/lib/with-account.ts:Update each handler to:
Streaming handlers must not retry once the SSE response has begun flushing.
Either:
fetch()upstream call, and once events startflowing, abort retry.
Definition of Done
withAccountis the only place that callspool.acquire/releaseoutside startup code.
account (use a dummy pool of two accounts).
on the other account; the dead account enters cooldown.
x-internal-pricing-sync: 1requests bypass nothing in thistask (the exemption only matters for the recorder in task 06).
docs/tasks/04-with-account-wrapper.mddocs/design/