diff --git a/extensions/copilot/src/extension/log/vscode-node/requestLogTree.ts b/extensions/copilot/src/extension/log/vscode-node/requestLogTree.ts index c0fd88c228c979..b60bf1b54b47a7 100644 --- a/extensions/copilot/src/extension/log/vscode-node/requestLogTree.ts +++ b/extensions/copilot/src/extension/log/vscode-node/requestLogTree.ts @@ -12,6 +12,7 @@ import * as tar from 'tar'; import * as vscode from 'vscode'; import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext'; import { outputChannel } from '../../../platform/log/vscode/outputChannelLogTarget'; +import type { APIUsage } from '../../../platform/networking/common/openai'; import { CapturingToken } from '../../../platform/requestLogger/common/capturingToken'; import { ChatRequestScheme, ILoggedElementInfo, ILoggedRequestInfo, ILoggedToolCall, IRequestLogger, LoggedInfo, LoggedInfoKind, LoggedRequestKind, resolveMarkdownIcon } from '../../../platform/requestLogger/common/requestLogger'; import { filterMap } from '../../../util/common/arrays'; @@ -31,6 +32,17 @@ const exportPromptArchiveCommand = 'github.copilot.chat.debug.exportPromptArchiv * Serialize MCP server definitions to a JSON-safe format. * Excludes sensitive headers like Authorization. */ +function tokenTotal(usage: APIUsage): number { + return usage.total_tokens ?? (usage.prompt_tokens + usage.completion_tokens); +} + +function formatTokenBreakdown(usage: APIUsage): string { + const fmt = (n: number) => n.toLocaleString('en-US'); + const cached = usage.prompt_tokens_details?.cached_tokens ?? 0; + const input = Math.max(0, usage.prompt_tokens - cached); + return `input: ${fmt(input)}, cached: ${fmt(cached)}, output: ${fmt(usage.completion_tokens)}`; +} + function serializeMcpServers(servers: readonly vscode.McpServerDefinition[]): object[] { return servers.map(server => { if (server instanceof vscode.McpStdioServerDefinition) { @@ -737,9 +749,38 @@ class ChatPromptItem extends vscode.TreeItem { item.collapsibleState = item.children.length > 0 ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.None; + item._applyTokenSummary(); return item; } + private _applyTokenSummary(): void { + let input = 0; + let cached = 0; + let output = 0; + let any = false; + for (const child of this.children) { + if (!(child instanceof ChatRequestItem) || child.info.entry.type !== LoggedRequestKind.ChatMLSuccess) { + continue; + } + const usage = child.info.entry.usage; + if (!usage) { + continue; + } + any = true; + const childCached = usage.prompt_tokens_details?.cached_tokens ?? 0; + input += Math.max(0, usage.prompt_tokens - childCached); + cached += childCached; + output += usage.completion_tokens; + } + if (!any) { + return; + } + const fmt = (n: number) => n.toLocaleString('en-US'); + const total = input + cached + output; + this.description = `[${fmt(total)}tks]`; + this.tooltip = `${this.label}\n\ttotal: ${fmt(total)}tks\n\tinput: ${fmt(input)}, cached: ${fmt(cached)}, output: ${fmt(output)}`; + } + } class ToolCallItem extends vscode.TreeItem { @@ -794,17 +835,18 @@ class ChatRequestItem extends vscode.TreeItem { const durationMs = info.entry.endTime.getTime() - info.entry.startTime.getTime(); const timeStr = `${durationMs.toLocaleString('en-US')}ms`; const startTimeStr = info.entry.startTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - const tokensStr = info.entry.type === LoggedRequestKind.ChatMLSuccess && info.entry.usage ? `${info.entry.usage.prompt_tokens.toLocaleString('en-US')}tks` : ''; - const tokensStrPart = tokensStr ? `[${tokensStr}] ` : ''; - this.description = `${tokensStrPart}[${timeStr}] [${startTimeStr}]`; + const usage = info.entry.type === LoggedRequestKind.ChatMLSuccess ? info.entry.usage : undefined; + const totalTokensStr = usage ? `${tokenTotal(usage).toLocaleString('en-US')}tks` : ''; + const totalTokensPart = totalTokensStr ? `[${totalTokensStr}] ` : ''; + this.description = `${totalTokensPart}[${timeStr}] [${startTimeStr}]`; this.iconPath = info.entry.type === LoggedRequestKind.ChatMLSuccess ? undefined : new vscode.ThemeIcon('error'); this.tooltip = `${info.entry.type === LoggedRequestKind.ChatMLCancelation ? 'cancelled' : info.entry.result.type} ${info.entry.chatEndpoint.model} ${timeStr} ${startTimeStr}`; - if (tokensStr) { - this.tooltip += `\n\t${tokensStr}`; + if (usage) { + this.tooltip += `\n\ttotal: ${totalTokensStr}\n\t${formatTokenBreakdown(usage)}`; } } this.command = {