diff --git a/packages/cli/src/ui/utils/export/collect.ts b/packages/cli/src/ui/utils/export/collect.ts index b0ea963f6..cd203da95 100644 --- a/packages/cli/src/ui/utils/export/collect.ts +++ b/packages/cli/src/ui/utils/export/collect.ts @@ -264,22 +264,36 @@ function extractTaskToolTokens(record: ChatRecord): number { /** * Calculate token statistics from ChatRecords. * Aggregates usageMetadata from assistant records and TaskTool executionSummary to get total token usage. + * Uses the last assistant record that has both totalTokenCount and contextWindowSize for calculating context usage percent. */ -function calculateTokenStats( - records: ChatRecord[], - contextWindowSize?: number, -): { totalTokens: number; contextUsagePercent?: number } { +function calculateTokenStats(records: ChatRecord[]): { + totalTokens: number; + contextUsagePercent?: number; + contextWindowSize?: number; +} { let totalTokens = 0; - let lastTotalTokens = 0; + // Track the last assistant record that has BOTH totalTokenCount and contextWindowSize + // to ensure the percentage calculation uses values from the same record + let lastValidRecord: { + totalTokenCount: number; + contextWindowSize: number; + } | null = null; // Aggregate usageMetadata from all assistant records - // Use last available totalTokenCount for context usage calculation for (const record of records) { - if (record.type === 'assistant' && record.usageMetadata) { - totalTokens += record.usageMetadata.totalTokenCount ?? 0; - // Use the last available totalTokenCount for context usage calculation - if (record.usageMetadata.totalTokenCount !== undefined) { - lastTotalTokens = record.usageMetadata.totalTokenCount; + if (record.type === 'assistant') { + if (record.usageMetadata) { + totalTokens += record.usageMetadata.totalTokenCount ?? 0; + } + // Only update lastValidRecord when BOTH values are present in the same record + if ( + record.usageMetadata?.totalTokenCount !== undefined && + record.contextWindowSize !== undefined + ) { + lastValidRecord = { + totalTokenCount: record.usageMetadata.totalTokenCount, + contextWindowSize: record.contextWindowSize, + }; } } @@ -290,17 +304,29 @@ function calculateTokenStats( } } - // Use last totalTokenCount for context usage calculation + // Use last valid record's values for context usage calculation // This represents how much of the context window is being used by the total tokens - if (contextWindowSize && lastTotalTokens > 0) { - const percent = (lastTotalTokens / contextWindowSize) * 100; + if (lastValidRecord) { + const percent = + (lastValidRecord.totalTokenCount / lastValidRecord.contextWindowSize) * + 100; return { totalTokens, contextUsagePercent: Math.round(percent * 10) / 10, + contextWindowSize: lastValidRecord.contextWindowSize, }; } - return { totalTokens }; + // Fallback: return the contextWindowSize from the last assistant record even if no valid pair found + // (for display purposes only, without percentage) + const lastAssistantRecord = [...records] + .reverse() + .find((r) => r.type === 'assistant' && r.contextWindowSize !== undefined); + + return { + totalTokens, + contextWindowSize: lastAssistantRecord?.contextWindowSize, + }; } /** @@ -343,25 +369,12 @@ async function extractMetadata( // Count user prompts const promptCount = messages.filter((m) => m.type === 'user').length; - // Get context window size - const contentGenConfig = config.getContentGeneratorConfig?.(); - const contextWindowSize = contentGenConfig?.contextWindowSize; - // Calculate file stats from original ChatRecords const fileStats = calculateFileStats(messages); // Calculate token stats from original ChatRecords - const tokenStats = calculateTokenStats(messages, contextWindowSize); - - // Extract the last response_id from assistant records (for request tracking) - let requestId: string | undefined; - for (let i = messages.length - 1; i >= 0; i--) { - const record = messages[i]; - if (record.type === 'assistant' && record.response_id) { - requestId = record.response_id; - break; - } - } + // contextWindowSize is retrieved from the last assistant record for accuracy + const tokenStats = calculateTokenStats(messages); return { sessionId, @@ -374,13 +387,12 @@ async function extractMetadata( channel, promptCount, contextUsagePercent: tokenStats.contextUsagePercent, - contextWindowSize, + contextWindowSize: tokenStats.contextWindowSize, totalTokens: tokenStats.totalTokens, filesWritten: fileStats.writtenFilePaths.size, linesAdded: fileStats.linesAdded, linesRemoved: fileStats.linesRemoved, uniqueFiles: Array.from(fileStats.writtenFilePaths), - requestId, }; } diff --git a/packages/cli/src/ui/utils/export/formatters/jsonl.ts b/packages/cli/src/ui/utils/export/formatters/jsonl.ts index e1d6939ba..4de132bb1 100644 --- a/packages/cli/src/ui/utils/export/formatters/jsonl.ts +++ b/packages/cli/src/ui/utils/export/formatters/jsonl.ts @@ -64,9 +64,6 @@ export function toJsonl(sessionData: ExportSessionData): string { if (sourceMetadata?.uniqueFiles && sourceMetadata.uniqueFiles.length > 0) { metadata['uniqueFiles'] = sourceMetadata.uniqueFiles; } - if (sourceMetadata?.requestId) { - metadata['requestId'] = sourceMetadata.requestId; - } lines.push(JSON.stringify(metadata)); diff --git a/packages/cli/src/ui/utils/export/formatters/markdown.ts b/packages/cli/src/ui/utils/export/formatters/markdown.ts index 443199f21..6ee18a754 100644 --- a/packages/cli/src/ui/utils/export/formatters/markdown.ts +++ b/packages/cli/src/ui/utils/export/formatters/markdown.ts @@ -21,11 +21,6 @@ export function toMarkdown(sessionData: ExportSessionData): string { `- **Exported**: ${sanitizeText(metadata?.exportTime ?? new Date().toISOString())}`, ); - // Add requestId if available - if (metadata?.requestId) { - lines.push(`- **Request ID**: \`${sanitizeText(metadata.requestId)}\``); - } - lines.push(''); // Add context info @@ -101,9 +96,6 @@ export function toMarkdown(sessionData: ExportSessionData): string { lines.push(formatMessageContent(message)); } else if (message.type === 'assistant') { lines.push('## Assistant\n'); - if (message.response_id) { - lines.push(`*Response ID: \`${sanitizeText(message.response_id)}\`*\n`); - } lines.push(formatMessageContent(message)); } else if (message.type === 'tool_call') { lines.push(formatToolCall(message)); diff --git a/packages/cli/src/ui/utils/export/normalize.ts b/packages/cli/src/ui/utils/export/normalize.ts index ae22f2cb5..cf9f80cdc 100644 --- a/packages/cli/src/ui/utils/export/normalize.ts +++ b/packages/cli/src/ui/utils/export/normalize.ts @@ -28,7 +28,7 @@ export function normalizeSessionData( } }); - // Build index of assistant messages by uuid for response_id mapping + // Build index of assistant messages by uuid for usageMetadata merging const assistantMessageIndexByUuid = new Map(); normalized.forEach((message, index) => { if (message.type === 'assistant') { @@ -66,17 +66,6 @@ export function normalizeSessionData( mergeToolCallData(existingMessage.toolCall, toolCallMessage.toolCall); } - // Merge response_id from assistant records - for (const record of originalRecords) { - if (record.type !== 'assistant') continue; - if (!record.response_id) continue; - - const existingIndex = assistantMessageIndexByUuid.get(record.uuid); - if (existingIndex !== undefined) { - normalized[existingIndex].response_id = record.response_id; - } - } - // Merge usageMetadata from assistant records for (const record of originalRecords) { if (record.type !== 'assistant') continue; diff --git a/packages/cli/src/ui/utils/export/types.ts b/packages/cli/src/ui/utils/export/types.ts index 03d4100b1..3148fb386 100644 --- a/packages/cli/src/ui/utils/export/types.ts +++ b/packages/cli/src/ui/utils/export/types.ts @@ -27,9 +27,6 @@ export interface ExportMessage { /** Model used for assistant messages */ model?: string; - /** Response ID from the LLM API for telemetry/tracing correlation */ - response_id?: string; - /** Token usage for this message (mainly for assistant messages) */ usageMetadata?: GenerateContentResponseUsageMetadata; @@ -88,8 +85,6 @@ export interface ExportMetadata { linesRemoved?: number; /** Unique files referenced in the session (written files only) */ uniqueFiles: string[]; - /** Last response ID from the LLM API (request ID) */ - requestId?: string; } /** diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index 979cca0a1..2d1cb5748 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -633,7 +633,6 @@ export class GeminiChat { // Collect ALL parts from the model response (including thoughts for recording) const allModelParts: Part[] = []; let usageMetadata: GenerateContentResponseUsageMetadata | undefined; - let responseId: string | undefined; let hasToolCall = false; let hasFinishReason = false; @@ -654,11 +653,6 @@ export class GeminiChat { // Collect all parts for recording allModelParts.push(...content.parts); } - - // Collect response ID for telemetry/tracing correlation - if (chunk.responseId) { - responseId = chunk.responseId; - } } // Collect token usage for consolidated recording @@ -730,6 +724,8 @@ export class GeminiChat { // Record assistant turn with raw Content and metadata if (thoughtContentPart || contentText || hasToolCall || usageMetadata) { + const contextWindowSize = + this.config.getContentGeneratorConfig()?.contextWindowSize; this.chatRecordingService?.recordAssistantTurn({ model, message: [ @@ -742,7 +738,7 @@ export class GeminiChat { : []), ], tokens: usageMetadata, - responseId, + contextWindowSize, }); } diff --git a/packages/core/src/services/chatRecordingService.ts b/packages/core/src/services/chatRecordingService.ts index 9ae4064a2..14f2f5ba7 100644 --- a/packages/core/src/services/chatRecordingService.ts +++ b/packages/core/src/services/chatRecordingService.ts @@ -81,8 +81,8 @@ export interface ChatRecord { usageMetadata?: GenerateContentResponseUsageMetadata; /** Model used for this response */ model?: string; - /** Response ID from the LLM API for telemetry/tracing correlation */ - response_id?: string; + /** Context window size of the model used for this response */ + contextWindowSize?: number; /** * Tool call metadata for UI recovery. * Contains enriched info (displayName, status, result, etc.) not in API format. @@ -301,14 +301,14 @@ export class ChatRecordingService { * @param data.message The raw PartListUnion object from the model response * @param data.model The model name * @param data.tokens Token usage statistics - * @param data.responseId Response ID from the LLM API + * @param data.contextWindowSize Context window size of the model * @param data.toolCallsMetadata Enriched tool call info for UI recovery */ recordAssistantTurn(data: { model: string; message?: PartListUnion; tokens?: GenerateContentResponseUsageMetadata; - responseId?: string; + contextWindowSize?: number; }): void { try { const record: ChatRecord = { @@ -324,8 +324,8 @@ export class ChatRecordingService { record.usageMetadata = data.tokens; } - if (data.responseId) { - record.response_id = data.responseId; + if (data.contextWindowSize !== undefined) { + record.contextWindowSize = data.contextWindowSize; } this.appendRecord(record); diff --git a/packages/web-templates/src/export-html/src/components/MetadataSidebar.tsx b/packages/web-templates/src/export-html/src/components/MetadataSidebar.tsx index 4b2d56086..ae5c5bd0c 100644 --- a/packages/web-templates/src/export-html/src/components/MetadataSidebar.tsx +++ b/packages/web-templates/src/export-html/src/components/MetadataSidebar.tsx @@ -11,81 +11,85 @@ export type MetadataSidebarProps = { }; export const MetadataSidebar = ({ metadata }: MetadataSidebarProps) => ( - +); diff --git a/packages/web-templates/src/export-html/src/components/types.ts b/packages/web-templates/src/export-html/src/components/types.ts index 94069c607..3fb562ad3 100644 --- a/packages/web-templates/src/export-html/src/components/types.ts +++ b/packages/web-templates/src/export-html/src/components/types.ts @@ -12,7 +12,6 @@ export type ChatData = { export type ExportMetadata = { sessionId: string; startTime: string; - relativeTime: string; exportTime: string; cwd: string; gitRepo?: string; @@ -28,7 +27,6 @@ export type ExportMetadata = { linesAdded?: number; linesRemoved?: number; uniqueFiles: string[]; - requestId?: string; }; export type PlatformContextValue = { diff --git a/packages/web-templates/src/export-html/src/styles.css b/packages/web-templates/src/export-html/src/styles.css index 6d66dcf12..df0f157e6 100644 --- a/packages/web-templates/src/export-html/src/styles.css +++ b/packages/web-templates/src/export-html/src/styles.css @@ -254,6 +254,13 @@ body { gap: 2px; } +.metadata-item-empty { + font-size: 12px; + color: #71717a; + margin: 0; + padding: 4px 0; +} + .metadata-content { display: flex; flex-direction: column;