f95d766f69
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>
91 lines
3.1 KiB
JavaScript
91 lines
3.1 KiB
JavaScript
function parseStreamJsonLine(line) {
|
|
const text = String(line ?? '').trim();
|
|
if (!text) return null;
|
|
try {
|
|
const parsed = JSON.parse(text);
|
|
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function parseCursorAgentStreamJsonEvents(stdout) {
|
|
const events = [];
|
|
for (const line of String(stdout ?? '').split('\n')) {
|
|
const parsed = parseStreamJsonLine(line);
|
|
if (parsed) events.push(parsed);
|
|
}
|
|
return events;
|
|
}
|
|
|
|
export function normalizeCursorAgentUsage(raw) {
|
|
if (!raw || typeof raw !== 'object') return null;
|
|
const inputTokens = Number(raw.inputTokens ?? raw.input_tokens ?? 0);
|
|
const outputTokens = Number(raw.outputTokens ?? raw.output_tokens ?? 0);
|
|
const cacheReadTokens = Number(raw.cacheReadTokens ?? raw.cache_read_tokens ?? 0);
|
|
const cacheWriteTokens = Number(raw.cacheWriteTokens ?? raw.cache_write_tokens ?? 0);
|
|
const billableInput = inputTokens + cacheWriteTokens;
|
|
const billableOutput = outputTokens;
|
|
if (billableInput <= 0 && billableOutput <= 0) return null;
|
|
return {
|
|
inputTokens: billableInput,
|
|
outputTokens: billableOutput,
|
|
cacheReadTokens: cacheReadTokens > 0 ? cacheReadTokens : 0,
|
|
cacheWriteTokens: cacheWriteTokens > 0 ? cacheWriteTokens : 0,
|
|
};
|
|
}
|
|
|
|
export function parseCursorAgentUsage(stdout) {
|
|
const events = parseCursorAgentStreamJsonEvents(stdout);
|
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
const event = events[index];
|
|
if (event?.type !== 'result') continue;
|
|
const usage = normalizeCursorAgentUsage(event.usage);
|
|
if (usage) return usage;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function extractAssistantText(message) {
|
|
if (!message || typeof message !== 'object') return '';
|
|
const content = message.content ?? message.message?.content;
|
|
if (typeof content === 'string') return content.trim();
|
|
if (!Array.isArray(content)) return '';
|
|
return content
|
|
.map((part) => {
|
|
if (!part || typeof part !== 'object') return '';
|
|
if (part.type === 'text') return String(part.text ?? '').trim();
|
|
return '';
|
|
})
|
|
.filter(Boolean)
|
|
.join('\n')
|
|
.trim();
|
|
}
|
|
|
|
export function extractCursorAgentDisplayText(stdout) {
|
|
const events = parseCursorAgentStreamJsonEvents(stdout);
|
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
const event = events[index];
|
|
if (event?.type === 'result') {
|
|
const resultText = String(event.result ?? '').trim();
|
|
if (resultText) return resultText;
|
|
}
|
|
if (event?.type === 'assistant') {
|
|
const assistantText = extractAssistantText(event.message ?? event);
|
|
if (assistantText) return assistantText;
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
export function buildCursorBillingTokenState(usage, priorBillingState = null) {
|
|
const normalized = normalizeCursorAgentUsage(usage);
|
|
if (!normalized) return null;
|
|
const priorIn = Number(priorBillingState?.lastInputTokens ?? 0);
|
|
const priorOut = Number(priorBillingState?.lastOutputTokens ?? 0);
|
|
return {
|
|
accumulatedInputTokens: priorIn + normalized.inputTokens,
|
|
accumulatedOutputTokens: priorOut + normalized.outputTokens,
|
|
};
|
|
}
|