feat(billing): bill Cursor executor usage via stream-json token parsing
Memind CI / Test, build, and release guards (push) Failing after 14m36s

Parse Cursor CLI usage from stream-json output and charge through billSessionUsage so subscription quota is consumed first and wallet overage applies when needed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-27 10:16:38 +08:00
parent 884e819f70
commit f95d766f69
9 changed files with 344 additions and 8 deletions
+89 -2
View File
@@ -44,6 +44,7 @@ import {
} from './executor-display-label.mjs';
import { applyCursorFirstAgentExecution } from './cursor-page-routing.mjs';
import { cursorDeepseekFallbackEnabled } from './cursor-agent-launch.mjs';
import { buildCursorBillingTokenState } from './cursor-agent-usage.mjs';
const DEFAULT_RUN_RETRY_DELAYS_MS = [1500, 5000, 15000];
const TERMINAL_STATUSES = new Set(['succeeded', 'failed']);
@@ -369,13 +370,83 @@ function summarizeText(value, limit = TOOL_GATEWAY_SUMMARY_LIMIT) {
export function buildCodeRunCompletionReply(result) {
const executor = resolveExecutorDisplayLabel(result?.executor, result?.executorLabel);
const output = sanitizeUserFacingBrandText(summarizeText(result?.stdout, 2400).trim());
const rawOutput = result?.displayStdout ?? result?.stdout;
const output = sanitizeUserFacingBrandText(summarizeText(rawOutput, 2400).trim());
return [
`已由 ${executor} 完成执行,并通过平台文件验收。`,
output ? `\n${output}` : '',
].join('').trim();
}
async function billCursorToolGatewayUsage({
userAuth,
userId,
sessionId,
requestId,
usage,
}) {
if (!usage || !sessionId || !userAuth?.billSessionUsage) return null;
const prior = typeof userAuth.getBillingState === 'function'
? await userAuth.getBillingState(sessionId).catch(() => null)
: null;
const tokenState = buildCursorBillingTokenState(usage, prior);
if (!tokenState) return null;
return userAuth.billSessionUsage(userId, sessionId, tokenState, requestId);
}
function resolveCursorBillingSessionId({ deliverySessionId, agentSessionId, runId }) {
const delivery = String(deliverySessionId ?? '').trim();
if (delivery) return delivery;
const session = String(agentSessionId ?? '').trim();
if (session) return session;
const run = String(runId ?? '').trim();
return run ? `h5run_${run}` : null;
}
async function recordCursorToolGatewayBilling({
appendEvent,
runId,
row,
result,
userAuth,
deliverySessionId = null,
}) {
if (String(result?.executor ?? '').trim().toLowerCase() !== 'cursor' || !result?.usage) {
return null;
}
const billingSessionId = resolveCursorBillingSessionId({
deliverySessionId,
agentSessionId: row.agent_session_id,
runId,
});
try {
const billing = await billCursorToolGatewayUsage({
userAuth,
userId: row.user_id,
sessionId: billingSessionId,
requestId: row.request_id,
usage: result.usage,
});
await appendEvent(runId, 'tool_gateway_billed', {
sessionId: billingSessionId,
executor: result.executor ?? null,
usage: result.usage,
billed: Boolean(billing?.ok),
costCents: billing?.costCents ?? 0,
deltaInputTokens: billing?.deltaInputTokens ?? 0,
deltaOutputTokens: billing?.deltaOutputTokens ?? 0,
});
return billing;
} catch (err) {
await appendEvent(runId, 'tool_gateway_billing_failed', {
sessionId: billingSessionId,
executor: result.executor ?? null,
message: err instanceof Error ? err.message : String(err),
});
return null;
}
}
function normalizeExpectedFileCheck(value) {
if (typeof value === 'string') {
const expectedPath = value.trim();
@@ -1778,8 +1849,9 @@ export function createAgentRunGateway({
executor: result.executor ?? null,
dryRun: Boolean(result.dryRun),
exitCode: result.exitCode ?? null,
stdoutTail: summarizeText(result.stdout),
stdoutTail: summarizeText(result.displayStdout ?? result.stdout),
stderrTail: summarizeText(result.stderr),
usage: result.usage ?? null,
});
try {
const validation = await validateToolGatewayResult({
@@ -1828,8 +1900,23 @@ export function createAgentRunGateway({
sessionId: delivery.sessionId,
executor: result.executor ?? null,
});
await recordCursorToolGatewayBilling({
appendEvent,
runId,
row,
result,
userAuth,
deliverySessionId: delivery.sessionId,
});
return { sessionId: delivery.sessionId, routing };
}
await recordCursorToolGatewayBilling({
appendEvent,
runId,
row,
result,
userAuth,
});
return { sessionId: row.agent_session_id ?? null, routing };
} catch (err) {
if (!(await fallbackCursorExecutorToDeepseek(err))) throw err;