mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
fix(ui): display output tokens instead of cumulative API throughput for subagents (#5972)
Multiple UI components displayed executionSummary.totalTokens (cumulative sum of total_tokens across all API rounds) as the subagent token count. For a subagent making 87 requests with ~51K prompt each, this inflated to 4.4M — misleading users into thinking it was context window usage. Switch all subagent token displays to use outputTokens (what the model actually generated), aligning with the loading indicator which already correctly shows output tokens only. Closes #5683
This commit is contained in:
parent
68348e236a
commit
9601d90b78
21 changed files with 108 additions and 81 deletions
|
|
@ -82,9 +82,9 @@ describe('ArenaSessionCard', () => {
|
|||
expect(output).toContain('Approach Summary:');
|
||||
expect(output).toContain('Refactored with JWT strategy pattern.');
|
||||
expect(output).toContain('Token Efficiency:');
|
||||
expect(output).toContain('45,000 tokens');
|
||||
expect(output).toContain('45,000 tokens · runtime 12.0s');
|
||||
expect(output).not.toContain('45,000 tokens · runtime 12.0s · 12 tools');
|
||||
expect(output).toContain('15,000 tokens');
|
||||
expect(output).toContain('15,000 tokens · runtime 12.0s');
|
||||
expect(output).not.toContain('15,000 tokens · runtime 12.0s · 12 tools');
|
||||
expect(output).not.toContain('Quick Preview:');
|
||||
expect(output).not.toContain('[View Detailed Diff]');
|
||||
expect(output).not.toContain('[Select Winner →]');
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ export const ArenaSessionCard: React.FC<ArenaSessionCardProps> = ({
|
|||
:{' '}
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
{agent.totalTokens.toLocaleString()} tokens · runtime{' '}
|
||||
{agent.outputTokens.toLocaleString()} tokens · runtime{' '}
|
||||
{formatDuration(agent.durationMs)}
|
||||
</Text>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export function ArenaSelectDialog({
|
|||
const label = agent.model.modelId;
|
||||
const statusInfo = getArenaStatusLabel(agent.status);
|
||||
const duration = formatDuration(agent.stats.durationMs);
|
||||
const tokens = agent.stats.totalTokens.toLocaleString();
|
||||
const tokens = agent.stats.outputTokens.toLocaleString();
|
||||
|
||||
// Build diff summary from cached result if available
|
||||
let diffAdditions = 0;
|
||||
|
|
@ -327,7 +327,7 @@ function ArenaAgentPreview({
|
|||
<Box marginLeft={2}>
|
||||
<Text color={theme.text.secondary}>Metrics: </Text>
|
||||
<Text color={theme.text.primary}>
|
||||
{result.stats.totalTokens.toLocaleString()} tokens ·{' '}
|
||||
{result.stats.outputTokens.toLocaleString()} tokens ·{' '}
|
||||
{formatDuration(result.stats.durationMs)} · {result.stats.toolCalls}{' '}
|
||||
tools
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ export function ArenaStatusDialog({
|
|||
// Use live stats from AgentInteractive when in-process, otherwise
|
||||
// fall back to the cached ArenaAgentState.stats (file-polled).
|
||||
const live = liveStats?.get(agent.agentId);
|
||||
const totalTokens = live?.totalTokens ?? agent.stats.totalTokens;
|
||||
const outputTokens = live?.outputTokens ?? agent.stats.outputTokens;
|
||||
const rounds = live?.rounds ?? agent.stats.rounds;
|
||||
const toolCalls = live?.totalToolCalls ?? agent.stats.toolCalls;
|
||||
const successfulToolCalls =
|
||||
|
|
@ -246,7 +246,7 @@ export function ArenaStatusDialog({
|
|||
</Box>
|
||||
<Box width={colTokens} justifyContent="flex-end">
|
||||
<Text color={theme.text.primary}>
|
||||
{pad(totalTokens.toLocaleString(), colTokens - 1, 'right')}
|
||||
{pad(outputTokens.toLocaleString(), colTokens - 1, 'right')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box width={colRounds} justifyContent="flex-end">
|
||||
|
|
|
|||
|
|
@ -594,10 +594,10 @@ const AgentDetailBody: React.FC<{
|
|||
|
||||
const terminal = terminalStatusPresentation(entry.status);
|
||||
const dimSubtitleParts: string[] = [elapsedFor(entry)];
|
||||
if (entry.stats?.totalTokens) {
|
||||
if (entry.stats?.outputTokens) {
|
||||
dimSubtitleParts.push(
|
||||
t('{{count}} tokens', {
|
||||
count: formatTokenCount(entry.stats.totalTokens),
|
||||
count: formatTokenCount(entry.stats.outputTokens),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,13 +292,18 @@ describe('<LiveAgentPanel />', () => {
|
|||
status: 'completed',
|
||||
startTime: -12_000,
|
||||
endTime: 0,
|
||||
stats: { totalTokens: 2400, toolUses: 5, durationMs: 12_000 },
|
||||
stats: {
|
||||
totalTokens: 2400,
|
||||
outputTokens: 800,
|
||||
toolUses: 5,
|
||||
durationMs: 12_000,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
const frame = lastFrame() ?? '';
|
||||
expect(frame).toContain('12s');
|
||||
expect(frame).toContain('2.4k tokens');
|
||||
expect(frame).toContain('800 tokens');
|
||||
});
|
||||
|
||||
it('renders paused agents with the paused glyph', () => {
|
||||
|
|
|
|||
|
|
@ -473,8 +473,8 @@ const AgentRow: React.FC<{
|
|||
? escapeAnsiCtrlCodes(entry.subagentType ?? '')
|
||||
: '';
|
||||
const tokenSuffix =
|
||||
entry.stats?.totalTokens && entry.stats.totalTokens > 0
|
||||
? ` · ${formatTokenCount(entry.stats.totalTokens)} tokens`
|
||||
entry.stats?.outputTokens && entry.stats.outputTokens > 0
|
||||
? ` · ${formatTokenCount(entry.stats.outputTokens)} tokens`
|
||||
: '';
|
||||
|
||||
// Layout (Claude Code's CoordinatorTaskPanel visual + our
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ describe('<InlineParallelAgentsDisplay />', () => {
|
|||
failedToolCalls: 0,
|
||||
successRate: 1,
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
outputTokens: 800,
|
||||
thoughtTokens: 0,
|
||||
cachedTokens: 0,
|
||||
totalTokens: 2400,
|
||||
|
|
@ -234,8 +234,8 @@ describe('<InlineParallelAgentsDisplay />', () => {
|
|||
const { lastFrame } = renderInline({ toolCalls: [toolCall] });
|
||||
const frame = lastFrame() ?? '';
|
||||
expect(frame).toContain('12s');
|
||||
// 2400 tokens → "2.4k" per formatTokenCount.
|
||||
expect(frame).toContain('2.4k tok');
|
||||
// outputTokens: 800 → "800" per formatTokenCount (not totalTokens: 2400).
|
||||
expect(frame).toContain('800 tok');
|
||||
});
|
||||
|
||||
it('ignores non task_execution tool calls in the same group', () => {
|
||||
|
|
|
|||
|
|
@ -246,8 +246,8 @@ export const InlineParallelAgentsDisplay: React.FC<
|
|||
: undefined,
|
||||
tokenCount:
|
||||
result.tokenCount ??
|
||||
live?.stats?.totalTokens ??
|
||||
result.executionSummary?.totalTokens,
|
||||
live?.stats?.outputTokens ??
|
||||
result.executionSummary?.outputTokens,
|
||||
};
|
||||
});
|
||||
}, [agentEntries, config, now]);
|
||||
|
|
|
|||
|
|
@ -401,8 +401,8 @@ const SubagentScrollbackSummary: React.FC<{
|
|||
formatDuration(stats.totalDurationMs, { hideTrailingZeros: true }),
|
||||
);
|
||||
}
|
||||
if (stats?.totalTokens && stats.totalTokens > 0) {
|
||||
parts.push(`${formatTokenCount(stats.totalTokens)} tokens`);
|
||||
if (stats?.outputTokens && stats.outputTokens > 0) {
|
||||
parts.push(`${formatTokenCount(stats.outputTokens)} tokens`);
|
||||
}
|
||||
// Sanitize every user/LLM-controlled string before it reaches Ink.
|
||||
// `subagentName` is subagent config (user-authored or model-chosen),
|
||||
|
|
|
|||
|
|
@ -433,6 +433,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
|
|
@ -524,6 +525,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
|
|
@ -662,6 +664,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
|
|
@ -739,6 +742,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
|
|
@ -829,6 +833,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
|
|
@ -911,6 +916,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
|
|
@ -995,6 +1001,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
|
|
@ -1021,8 +1028,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
expect(registry.get(agentId)?.status).toBe('completed');
|
||||
});
|
||||
const provider = subagent.setExternalMessageProvider.mock.calls[0]?.[0] as
|
||||
| (() => string[])
|
||||
| undefined;
|
||||
(() => string[]) | undefined;
|
||||
expect(provider).toBeDefined();
|
||||
expect(provider?.()).toEqual(['second message']);
|
||||
});
|
||||
|
|
@ -1081,7 +1087,11 @@ describe('BackgroundAgentResumeService', () => {
|
|||
setExternalMessageWaiter: vi.fn(),
|
||||
setExternalMessageWaitPredicate: vi.fn(),
|
||||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({ totalTokens: 0, totalDurationMs: 0 }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
getFinalText: () => 'done',
|
||||
};
|
||||
|
|
@ -1193,7 +1203,11 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: vi.fn(() => {
|
||||
throw new Error('setup failed');
|
||||
}),
|
||||
getExecutionSummary: () => ({ totalTokens: 0, totalDurationMs: 0 }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
getFinalText: () => 'done',
|
||||
};
|
||||
|
|
@ -1324,6 +1338,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
|
|
@ -1363,8 +1378,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
const executeCall = execute.mock.calls[0];
|
||||
expect(executeCall).toBeDefined();
|
||||
const contextArg = executeCall?.[0] as
|
||||
| { get(key: string): unknown }
|
||||
| undefined;
|
||||
{ get(key: string): unknown } | undefined;
|
||||
expect(contextArg).toBeDefined();
|
||||
if (!contextArg) {
|
||||
throw new Error('Expected resume execute context');
|
||||
|
|
@ -1601,6 +1615,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.CANCELLED,
|
||||
|
|
@ -1679,6 +1694,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.CANCELLED,
|
||||
|
|
@ -1765,8 +1781,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
const execute = vi.fn(
|
||||
async (context: { get: (key: string) => unknown }) => {
|
||||
const override = context.get('initial_messages_override') as
|
||||
| Array<{ parts?: Array<{ text?: string }> }>
|
||||
| undefined;
|
||||
Array<{ parts?: Array<{ text?: string }> }> | undefined;
|
||||
expect(override).toBeUndefined();
|
||||
expect(context.get('task_prompt')).toBe('continue work');
|
||||
},
|
||||
|
|
@ -1777,6 +1792,7 @@ describe('BackgroundAgentResumeService', () => {
|
|||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
|
|
@ -1872,7 +1888,11 @@ describe('BackgroundAgentResumeService', () => {
|
|||
execute,
|
||||
setExternalMessageProvider: vi.fn(),
|
||||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({ totalTokens: 0, totalDurationMs: 0 }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
getFinalText: () => 'iterated',
|
||||
};
|
||||
|
|
@ -2196,7 +2216,11 @@ describe('BackgroundAgentResumeService', () => {
|
|||
execute: vi.fn(async () => undefined),
|
||||
setExternalMessageProvider: vi.fn(),
|
||||
getCore: () => ({ getEventEmitter: () => new AgentEventEmitter() }),
|
||||
getExecutionSummary: () => ({ totalTokens: 0, totalDurationMs: 0 }),
|
||||
getExecutionSummary: () => ({
|
||||
totalTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalDurationMs: 0,
|
||||
}),
|
||||
getTerminateMode: () => AgentTerminateMode.GOAL,
|
||||
getFinalText: () => 'iterated',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -325,8 +325,7 @@ function recoverTranscript(records: ChatRecord[]): TranscriptRecovery {
|
|||
'fork' &&
|
||||
typeof (
|
||||
launchPromptRecord?.systemPayload as
|
||||
| NotificationRecordPayload
|
||||
| undefined
|
||||
NotificationRecordPayload | undefined
|
||||
)?.displayText === 'string'
|
||||
? {
|
||||
history: structuredClone(
|
||||
|
|
@ -362,6 +361,7 @@ function getCompletionStats(
|
|||
const summary = subagent.getExecutionSummary();
|
||||
return {
|
||||
totalTokens: summary.totalTokens,
|
||||
outputTokens: summary.outputTokens,
|
||||
toolUses: liveToolCallCount,
|
||||
durationMs: summary.totalDurationMs,
|
||||
};
|
||||
|
|
@ -699,7 +699,7 @@ export class BackgroundAgentResumeService {
|
|||
// definition would silently auto-deny calls the fresh launch bubbles.
|
||||
const shouldBubble = Boolean(
|
||||
target.subagentConfig?.approvalMode === BUBBLE_APPROVAL_MODE &&
|
||||
this.config.isInteractive(),
|
||||
this.config.isInteractive(),
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const bgConfig = Object.create(agentConfig) as any;
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ export type BackgroundTaskStatus = TaskStatus;
|
|||
|
||||
export interface AgentCompletionStats {
|
||||
totalTokens: number;
|
||||
outputTokens: number;
|
||||
toolUses: number;
|
||||
durationMs: number;
|
||||
}
|
||||
|
|
@ -859,8 +860,7 @@ export class BackgroundTaskRegistry {
|
|||
*/
|
||||
reset(): void {
|
||||
const firstEntry = this.agents.values().next().value as
|
||||
| AgentTask
|
||||
| undefined;
|
||||
AgentTask | undefined;
|
||||
if (!firstEntry) return;
|
||||
for (const entry of this.agents.values()) {
|
||||
// Defensive: callers (session switch via /resume, /clear) gate on
|
||||
|
|
|
|||
|
|
@ -1277,8 +1277,7 @@ class AgentToolInvocation extends BaseToolInvocation<AgentParams, ToolResult> {
|
|||
|
||||
promptConfig = {
|
||||
renderedSystemPrompt: generationConfig.systemInstruction as
|
||||
| string
|
||||
| Content,
|
||||
string | Content,
|
||||
initialMessages,
|
||||
};
|
||||
toolConfig = {
|
||||
|
|
@ -2225,7 +2224,7 @@ class AgentToolInvocation extends BaseToolInvocation<AgentParams, ToolResult> {
|
|||
// already requires confirmation — this only flips deny → surface.)
|
||||
const shouldBubble = Boolean(
|
||||
subagentConfig.approvalMode === BUBBLE_APPROVAL_MODE &&
|
||||
this.config.isInteractive(),
|
||||
this.config.isInteractive(),
|
||||
);
|
||||
// Use Object.create so the resolved approval mode override (e.g.
|
||||
// subagent-level `approvalMode: auto-edit`) is preserved.
|
||||
|
|
@ -2422,6 +2421,7 @@ class AgentToolInvocation extends BaseToolInvocation<AgentParams, ToolResult> {
|
|||
const summary = bgSubagent.getExecutionSummary();
|
||||
entry.stats = {
|
||||
totalTokens: summary.totalTokens,
|
||||
outputTokens: summary.outputTokens,
|
||||
toolUses: liveToolCallCount,
|
||||
durationMs: summary.totalDurationMs,
|
||||
};
|
||||
|
|
@ -2473,6 +2473,7 @@ class AgentToolInvocation extends BaseToolInvocation<AgentParams, ToolResult> {
|
|||
const summary = bgSubagent.getExecutionSummary();
|
||||
return {
|
||||
totalTokens: summary.totalTokens,
|
||||
outputTokens: summary.outputTokens,
|
||||
toolUses: liveToolCallCount,
|
||||
durationMs: summary.totalDurationMs,
|
||||
};
|
||||
|
|
@ -2879,6 +2880,7 @@ class AgentToolInvocation extends BaseToolInvocation<AgentParams, ToolResult> {
|
|||
const summary = subagent.getExecutionSummary();
|
||||
entry.stats = {
|
||||
totalTokens: summary.totalTokens,
|
||||
outputTokens: summary.outputTokens,
|
||||
toolUses: fgLiveToolCallCount,
|
||||
durationMs: summary.totalDurationMs,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -677,15 +677,21 @@ function TaskDetail({
|
|||
},
|
||||
];
|
||||
|
||||
if (task.kind === 'agent' && task.stats?.totalTokens) {
|
||||
const agentOutputTokens =
|
||||
task.kind === 'agent'
|
||||
? (((task.stats as Record<string, unknown> | undefined)?.[
|
||||
'outputTokens'
|
||||
] as number | undefined) ?? task.stats?.totalTokens)
|
||||
: undefined;
|
||||
if (agentOutputTokens) {
|
||||
subtitleParts.push(
|
||||
t('tasks.detail.tokens', {
|
||||
count: formatTokenCount(task.stats.totalTokens),
|
||||
count: formatTokenCount(agentOutputTokens),
|
||||
}),
|
||||
);
|
||||
compactFields.push({
|
||||
label: t('tasks.detail.tokenCount'),
|
||||
value: formatTokenCount(task.stats.totalTokens),
|
||||
value: formatTokenCount(agentOutputTokens),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -392,8 +392,7 @@ function getAgentDisplayInfo(
|
|||
0;
|
||||
|
||||
const stats = taskExec?.['executionSummary'] as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
Record<string, unknown> | undefined;
|
||||
const elapsed =
|
||||
stats && typeof stats['totalDurationMs'] === 'number'
|
||||
? formatDurationMs(stats['totalDurationMs'])
|
||||
|
|
@ -403,17 +402,17 @@ function getAgentDisplayInfo(
|
|||
(tool.status === 'in_progress' && now ? now : undefined),
|
||||
);
|
||||
|
||||
const totalTokens =
|
||||
const outputTokens =
|
||||
taskExec &&
|
||||
typeof taskExec['tokenCount'] === 'number' &&
|
||||
taskExec['tokenCount'] > 0
|
||||
? (taskExec['tokenCount'] as number)
|
||||
: stats &&
|
||||
typeof stats['totalTokens'] === 'number' &&
|
||||
stats['totalTokens'] > 0
|
||||
? (stats['totalTokens'] as number)
|
||||
typeof stats['outputTokens'] === 'number' &&
|
||||
stats['outputTokens'] > 0
|
||||
? (stats['outputTokens'] as number)
|
||||
: 0;
|
||||
const tokens = totalTokens > 0 ? formatTokenCount(totalTokens) : '';
|
||||
const tokens = outputTokens > 0 ? formatTokenCount(outputTokens) : '';
|
||||
|
||||
return {
|
||||
agentType,
|
||||
|
|
|
|||
|
|
@ -37,8 +37,7 @@ function getAgentStats(agent: ACPToolCall, now: number): string {
|
|||
const parts: string[] = [];
|
||||
const taskExec = getTaskExecutionRecord(agent.rawOutput);
|
||||
const stats = taskExec?.['executionSummary'] as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
Record<string, unknown> | undefined;
|
||||
const elapsed =
|
||||
stats && typeof stats['totalDurationMs'] === 'number'
|
||||
? formatDuration(stats['totalDurationMs'])
|
||||
|
|
@ -53,9 +52,9 @@ function getAgentStats(agent: ACPToolCall, now: number): string {
|
|||
taskExec['tokenCount'] > 0
|
||||
? (taskExec['tokenCount'] as number)
|
||||
: stats &&
|
||||
typeof stats['totalTokens'] === 'number' &&
|
||||
stats['totalTokens'] > 0
|
||||
? (stats['totalTokens'] as number)
|
||||
typeof stats['outputTokens'] === 'number' &&
|
||||
stats['outputTokens'] > 0
|
||||
? (stats['outputTokens'] as number)
|
||||
: 0;
|
||||
if (tokens > 0) {
|
||||
parts.push(formatTokenCount(tokens));
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ export function SubAgentPanel({
|
|||
const tokenCount =
|
||||
taskExec?.tokenCount && taskExec.tokenCount > 0
|
||||
? taskExec.tokenCount
|
||||
: taskExec?.executionSummary?.totalTokens;
|
||||
: taskExec?.executionSummary?.outputTokens;
|
||||
const tokens = tokenCount ? formatTokenCount(tokenCount) : '';
|
||||
const resultText = isComplete ? getAgentResultText(tool) : '';
|
||||
|
||||
|
|
|
|||
|
|
@ -178,8 +178,9 @@ function getTaskExecutionTokenCount(rawOutput: unknown): number | undefined {
|
|||
if (typeof tokenCount === 'number' && tokenCount > 0) return tokenCount;
|
||||
const summary = obj['executionSummary'];
|
||||
if (typeof summary === 'object' && summary !== null) {
|
||||
const totalTokens = (summary as Record<string, unknown>)['totalTokens'];
|
||||
if (typeof totalTokens === 'number' && totalTokens > 0) return totalTokens;
|
||||
const outputTokens = (summary as Record<string, unknown>)['outputTokens'];
|
||||
if (typeof outputTokens === 'number' && outputTokens > 0)
|
||||
return outputTokens;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ export const isAgentExecutionRawOutput = (
|
|||
): value is AgentExecutionRawOutput =>
|
||||
Boolean(
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
'type' in value &&
|
||||
(value as { type?: unknown }).type === 'task_execution' &&
|
||||
'taskDescription' in value &&
|
||||
'status' in value,
|
||||
typeof value === 'object' &&
|
||||
'type' in value &&
|
||||
(value as { type?: unknown }).type === 'task_execution' &&
|
||||
'taskDescription' in value &&
|
||||
'status' in value,
|
||||
);
|
||||
|
||||
export const isAgentExecutionToolCall = (
|
||||
|
|
@ -136,7 +136,11 @@ export const AgentToolCall: FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||
<div className="flex flex-wrap gap-x-4 gap-y-1">
|
||||
<span>{data.executionSummary.totalToolCalls} tool calls</span>
|
||||
<span>
|
||||
{data.executionSummary.totalTokens.toLocaleString()} tokens
|
||||
{(
|
||||
data.executionSummary.outputTokens ??
|
||||
data.executionSummary.totalTokens
|
||||
).toLocaleString()}{' '}
|
||||
tokens
|
||||
</span>
|
||||
<span>{formatDuration(data.executionSummary.totalDurationMs)}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,27 +36,18 @@ export interface ToolCallLocation {
|
|||
* Tool call status type
|
||||
*/
|
||||
export type ToolCallStatus =
|
||||
| 'pending'
|
||||
| 'in_progress'
|
||||
| 'completed'
|
||||
| 'failed'
|
||||
| 'cancelled';
|
||||
'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
|
||||
|
||||
export type AgentExecutionStatus =
|
||||
| 'running'
|
||||
| 'completed'
|
||||
| 'failed'
|
||||
| 'cancelled';
|
||||
'running' | 'completed' | 'failed' | 'cancelled';
|
||||
|
||||
export type AgentToolCallStatus =
|
||||
| 'executing'
|
||||
| 'awaiting_approval'
|
||||
| 'success'
|
||||
| 'failed';
|
||||
'executing' | 'awaiting_approval' | 'success' | 'failed';
|
||||
|
||||
export interface AgentExecutionSummary {
|
||||
totalToolCalls: number;
|
||||
totalTokens: number;
|
||||
outputTokens?: number;
|
||||
totalDurationMs: number;
|
||||
successfulToolCalls?: number;
|
||||
failedToolCalls?: number;
|
||||
|
|
@ -132,11 +123,7 @@ export interface GroupedContent {
|
|||
* Container status type for styling
|
||||
*/
|
||||
export type ContainerStatus =
|
||||
| 'success'
|
||||
| 'error'
|
||||
| 'warning'
|
||||
| 'loading'
|
||||
| 'default';
|
||||
'success' | 'error' | 'warning' | 'loading' | 'default';
|
||||
|
||||
/**
|
||||
* Plan entry status type
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue