Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 47 additions & 5 deletions extensions/copilot/src/extension/log/vscode-node/requestLogTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 = {
Expand Down
Loading