Skip to content
Open
Show file tree
Hide file tree
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
37 changes: 37 additions & 0 deletions packages/core/src/PageAgentCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
HistoricalEvent,
MacroToolInput,
MacroToolResult,
TokenUsageStats,
} from './types'
import { assert, fetchLlmsTxt, normalizeResponse, uid, waitFor } from './utils'

Expand Down Expand Up @@ -193,6 +194,29 @@ export class PageAgentCore extends EventTarget {
this.#abortController.abort()
}

/** Aggregate token usage from all step events in history */
getTokenUsageStats(): TokenUsageStats {
const stats: TokenUsageStats = {
totalPromptTokens: 0,
totalCompletionTokens: 0,
totalTokens: 0,
cachedTokens: 0,
reasoningTokens: 0,
steps: 0,
}
for (const event of this.history) {
if (event.type === 'step' && event.usage) {
stats.totalPromptTokens += event.usage.promptTokens
stats.totalCompletionTokens += event.usage.completionTokens
stats.totalTokens += event.usage.totalTokens
stats.cachedTokens += event.usage.cachedTokens ?? 0
stats.reasoningTokens += event.usage.reasoningTokens ?? 0
stats.steps++
}
}
return stats
}

async execute(task: string): Promise<ExecutionResult> {
if (this.disposed) throw new Error('PageAgent has been disposed. Create a new instance.')
if (!task) throw new Error('Task is required')
Expand Down Expand Up @@ -300,12 +324,23 @@ export class PageAgentCore extends EventTarget {
if (actionName === 'done') {
const success = action.input?.success ?? false
const text = action.input?.text || 'no text provided'
const tokenUsage = this.getTokenUsageStats()
console.log(chalk.green.bold('Task completed'), success, text)
console.log(
chalk.cyan.bold('📊 Token Usage:'),
`${tokenUsage.steps} steps |`,
`Prompt: ${tokenUsage.totalPromptTokens} |`,
`Completion: ${tokenUsage.totalCompletionTokens} |`,
`Total: ${tokenUsage.totalTokens}`,
tokenUsage.cachedTokens ? `| Cached: ${tokenUsage.cachedTokens}` : '',
tokenUsage.reasoningTokens ? `| Reasoning: ${tokenUsage.reasoningTokens}` : ''
)
this.#onDone(success)
const result: ExecutionResult = {
success,
data: text,
history: this.history,
tokenUsage,
}
await onAfterTask?.(this, result)
return result
Expand All @@ -324,6 +359,7 @@ export class PageAgentCore extends EventTarget {
success: false,
data: errorMessage,
history: this.history,
tokenUsage: this.getTokenUsageStats(),
}
await onAfterTask?.(this, result)
return result
Expand All @@ -339,6 +375,7 @@ export class PageAgentCore extends EventTarget {
success: false,
data: errorMessage,
history: this.history,
tokenUsage: this.getTokenUsageStats(),
}
await onAfterTask?.(this, result)
return result
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,18 @@ export type HistoricalEvent =
*/
export type AgentStatus = 'idle' | 'running' | 'completed' | 'error'

/**
* Aggregated token usage statistics across all steps in a task
*/
export interface TokenUsageStats {
totalPromptTokens: number
totalCompletionTokens: number
totalTokens: number
cachedTokens: number
reasoningTokens: number
steps: number
}

/**
* Agent activity - transient state for immediate UI feedback.
*
Expand All @@ -286,4 +298,5 @@ export interface ExecutionResult {
success: boolean
data: string
history: HistoricalEvent[]
tokenUsage: TokenUsageStats
}
3 changes: 2 additions & 1 deletion packages/page-controller/src/dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
* @note Since isTopElement depends on elementFromPoint,
* it returns null when out of viewport, this feature has no practical use, only differ between -1 and 0
*/
const DEFAULT_VIEWPORT_EXPANSION = -1
// const DEFAULT_VIEWPORT_EXPANSION = -1
const DEFAULT_VIEWPORT_EXPANSION = 0

export function resolveViewportExpansion(viewportExpansion?: number): number {
return viewportExpansion ?? DEFAULT_VIEWPORT_EXPANSION
Expand Down
14 changes: 14 additions & 0 deletions packages/ui/src/i18n/locales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ const enUS = {
expand: 'Expand history',
collapse: 'Collapse history',
step: 'Step {{number}}',
tokenUsage: 'Token Usage',
tokenSteps: '{{count}} steps',
promptTokens: 'Prompt: {{count}}',
completionTokens: 'Completion: {{count}}',
totalTokens: 'Total: {{count}}',
cachedTokens: 'Cached: {{count}}',
reasoningTokens: 'Reasoning: {{count}}',
},
tools: {
clicking: 'Clicking element [{{index}}]...',
Expand Down Expand Up @@ -64,6 +71,13 @@ const zhCN = {
expand: '展开历史',
collapse: '收起历史',
step: '步骤 {{number}}',
tokenUsage: 'Token 用量',
tokenSteps: '{{count}} 步',
promptTokens: '提示: {{count}}',
completionTokens: '补全: {{count}}',
totalTokens: '总计: {{count}}',
cachedTokens: '缓存: {{count}}',
reasoningTokens: '推理: {{count}}',
},
tools: {
clicking: '正在点击元素 [{{index}}]...',
Expand Down
5 changes: 5 additions & 0 deletions packages/ui/src/panel/Panel.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,11 @@
background: linear-gradient(135deg, rgba(255, 159, 67, 0.15), rgba(255, 159, 67, 0.08));
}

&.tokenStats {
border-left-color: rgb(56, 189, 248);
background: linear-gradient(135deg, rgba(56, 189, 248, 0.12), rgba(56, 189, 248, 0.05));
}

/* 突出显示 done 成功结果 */
&.doneSuccess {
background: linear-gradient(
Expand Down
57 changes: 57 additions & 0 deletions packages/ui/src/panel/Panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export class Panel {
if (this.#shouldShowInputArea()) {
this.#showInputArea()
}
this.#renderHistory()
}
}

Expand Down Expand Up @@ -580,10 +581,66 @@ export class Panel {
items.push(...this.#createHistoryCards(event))
}

// 3. Token usage summary card (after task ends)
const status = this.#agent.status
if (status === 'completed' || status === 'error') {
const statsCard = this.#createTokenStatsCard()
if (statsCard) {
items.push(statsCard)
}
}

this.#historySection.innerHTML = items.join('')
this.#scrollToBottom()
}

/** Create token usage stats summary card */
#createTokenStatsCard(): string | null {
const history = this.#agent.history
let totalPromptTokens = 0
let totalCompletionTokens = 0
let totalTokens = 0
let cachedTokens = 0
let reasoningTokens = 0
let steps = 0

for (const event of history) {
if (event.type === 'step' && event.usage) {
totalPromptTokens += event.usage.promptTokens
totalCompletionTokens += event.usage.completionTokens
totalTokens += event.usage.totalTokens
cachedTokens += event.usage.cachedTokens ?? 0
reasoningTokens += event.usage.reasoningTokens ?? 0
steps++
}
}

if (steps === 0) return null

const lines: string[] = [
this.#i18n.t('ui.panel.tokenSteps', { count: steps }),
this.#i18n.t('ui.panel.promptTokens', { count: totalPromptTokens.toLocaleString() }),
this.#i18n.t('ui.panel.completionTokens', { count: totalCompletionTokens.toLocaleString() }),
this.#i18n.t('ui.panel.totalTokens', { count: totalTokens.toLocaleString() }),
]

if (cachedTokens > 0) {
lines.push(this.#i18n.t('ui.panel.cachedTokens', { count: cachedTokens.toLocaleString() }))
}
if (reasoningTokens > 0) {
lines.push(
this.#i18n.t('ui.panel.reasoningTokens', { count: reasoningTokens.toLocaleString() })
)
}

return createCard({
icon: '📊',
content: lines,
meta: this.#i18n.t('ui.panel.tokenUsage'),
type: 'tokenStats',
})
}

#createTaskCard(task: string): string {
return createCard({ icon: '🎯', content: task, type: 'input' })
}
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/panel/cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { escapeHtml } from '../utils'

import styles from './Panel.module.css'

type CardType = 'default' | 'input' | 'output' | 'question' | 'observation'
export type CardType = 'default' | 'input' | 'output' | 'question' | 'observation' | 'tokenStats'

interface CardOptions {
icon: string
Expand Down
8 changes: 8 additions & 0 deletions packages/ui/src/panel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ export interface PanelAgentAdapter extends EventTarget {
input: unknown
output: string
}
/** For 'step' type - token usage */
usage?: {
promptTokens: number
completionTokens: number
totalTokens: number
cachedTokens?: number
reasoningTokens?: number
}
/** For 'observation' type */
content?: string
/** For 'retry' type */
Expand Down