fix(billing): read Goose accumulated_cost from PostgreSQL fallback

Goosed GET /sessions/:id returns accumulated_cost null while memind_sessions
PG has the real upstream cost. Fall back to PG before token estimate billing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-05 15:48:39 +08:00
parent e2014e05e6
commit 8c1ae7550d
5 changed files with 166 additions and 24 deletions
+36 -13
View File
@@ -1,4 +1,5 @@
import { normalizeTokenState } from './billing.mjs';
import { fetchGooseSessionAccumulatedCostUsd } from './goose-session-cost.mjs';
export function loadCostEstimateConfig(env = process.env) {
const useBackendCost = env.H5_USE_BACKEND_COST === '1';
@@ -56,22 +57,44 @@ export function enrichTokenStateForBilling(
return state;
}
async function resolveSessionCostPayload(sessionId, fetchSession, fetchSessionCostFromPg, env) {
let sessionCost = null;
if (typeof fetchSession === 'function' && sessionId) {
try {
sessionCost = await fetchSession(sessionId);
} catch {
sessionCost = null;
}
}
if (pickSessionAccumulatedCost(sessionCost) != null) {
return sessionCost;
}
const readPgCost =
typeof fetchSessionCostFromPg === 'function'
? fetchSessionCostFromPg
: (sid) => fetchGooseSessionAccumulatedCostUsd(sid, env);
const pgUsd = env.H5_USE_BACKEND_COST === '1' ? await readPgCost(sessionId) : null;
if (pgUsd != null) {
return {
...(sessionCost && typeof sessionCost === 'object' ? sessionCost : {}),
accumulated_cost: pgUsd,
};
}
return sessionCost;
}
export async function resolveBillingTokenState(
tokenStateRaw,
{ sessionId = null, fetchSession = null } = {},
{ sessionId = null, fetchSession = null, fetchSessionCostFromPg = null } = {},
env = process.env,
) {
const state = normalizeTokenState(tokenStateRaw);
if (typeof fetchSession === 'function' && sessionId) {
try {
const session = await fetchSession(sessionId);
const enriched = enrichTokenStateForBilling(state, { sessionCost: session }, env);
if (enriched.accumulatedCost != null) return enriched;
} catch {
// Best-effort: fall through to inline cost / token estimate.
}
}
return enrichTokenStateForBilling(state, {}, env);
const sessionCost = await resolveSessionCostPayload(
sessionId,
fetchSession,
fetchSessionCostFromPg,
env,
);
return enrichTokenStateForBilling(state, { sessionCost }, env);
}