Files
memind/billing-token-state.mjs
T
john e7f0627dc9 fix(billing): prefer Goose session cost over token estimate
Billing fell back to inflated token list-price estimates when Finish
frames carried inline accumulatedCost, causing ~70x overcharge vs DeepSeek.
Always fetch session accumulated_cost first; add Aug 4+ compensation script.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 09:52:13 +08:00

78 lines
2.7 KiB
JavaScript

import { normalizeTokenState } from './billing.mjs';
export function loadCostEstimateConfig(env = process.env) {
const useBackendCost = env.H5_USE_BACKEND_COST === '1';
const enabled = useBackendCost && env.H5_COST_ESTIMATE_FROM_TOKENS !== '0';
const inputUsdPer1M = Number(env.H5_COST_ESTIMATE_INPUT_USD_PER_1M ?? 0.27);
const outputUsdPer1M = Number(env.H5_COST_ESTIMATE_OUTPUT_USD_PER_1M ?? 1.1);
return {
enabled,
inputUsdPer1M: Number.isFinite(inputUsdPer1M) && inputUsdPer1M >= 0 ? inputUsdPer1M : 0.27,
outputUsdPer1M: Number.isFinite(outputUsdPer1M) && outputUsdPer1M >= 0 ? outputUsdPer1M : 1.1,
};
}
export function pickSessionAccumulatedCost(session) {
if (!session || typeof session !== 'object') return null;
const raw = session.accumulatedCost ?? session.accumulated_cost ?? null;
if (raw == null) return null;
const value = Number(raw);
return Number.isFinite(value) && value >= 0 ? value : null;
}
export function estimateAccumulatedCostUsd(tokenStateRaw, estimateConfig = loadCostEstimateConfig()) {
if (!estimateConfig?.enabled) return null;
const state = normalizeTokenState(tokenStateRaw);
if (state.accumulatedInputTokens <= 0 && state.accumulatedOutputTokens <= 0) return null;
const inputUsd =
(state.accumulatedInputTokens * estimateConfig.inputUsdPer1M) / 1_000_000;
const outputUsd =
(state.accumulatedOutputTokens * estimateConfig.outputUsdPer1M) / 1_000_000;
const total = inputUsd + outputUsd;
return total > 0 ? total : null;
}
export function enrichTokenStateForBilling(
tokenStateRaw,
{ sessionCost = null } = {},
env = process.env,
) {
const state = normalizeTokenState(tokenStateRaw);
const sessionUsd = pickSessionAccumulatedCost(sessionCost);
if (sessionUsd != null) {
return { ...state, accumulatedCost: sessionUsd };
}
if (state.accumulatedCost != null && Number(state.accumulatedCost) >= 0) {
return state;
}
const estimatedUsd = estimateAccumulatedCostUsd(state, loadCostEstimateConfig(env));
if (estimatedUsd != null) {
return { ...state, accumulatedCost: estimatedUsd };
}
return state;
}
export async function resolveBillingTokenState(
tokenStateRaw,
{ sessionId = null, fetchSession = 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);
}