32fb2cdeaf
Add chat file/image upload UX, attachment proxying, vision thumbnails, and per-turn image scoping so agents only use the current upload. Extend MindSpace asset context, billing token state, OA/scenario verify scripts, and related runtime config. Co-authored-by: Cursor <cursoragent@cursor.com>
80 lines
2.8 KiB
JavaScript
80 lines
2.8 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);
|
|
if (state.accumulatedCost != null && Number(state.accumulatedCost) >= 0) {
|
|
return state;
|
|
}
|
|
|
|
const sessionUsd = pickSessionAccumulatedCost(sessionCost);
|
|
if (sessionUsd != null) {
|
|
return { ...state, accumulatedCost: sessionUsd };
|
|
}
|
|
|
|
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 (state.accumulatedCost != null && Number(state.accumulatedCost) >= 0) {
|
|
return state;
|
|
}
|
|
|
|
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 token estimate.
|
|
}
|
|
}
|
|
|
|
return enrichTokenStateForBilling(state, {}, env);
|
|
}
|