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, }; }