Skip to content

Commit ce39246

Browse files
committed
feat(telemetry): add controller telemetry tracking and display
- Add telemetry callback to controller execution - Implement controller telemetry state updates - Display controller and step token counts in output window - Refactor telemetry update logic into shared utility
1 parent 4cfdee5 commit ce39246

File tree

4 files changed

+65
-30
lines changed

4 files changed

+65
-30
lines changed

src/cli/tui/routes/workflow/components/output/output-window.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -425,11 +425,25 @@ export function OutputWindow(props: OutputWindowProps) {
425425
</Show>
426426
</box>
427427
<text fg={themeCtx.theme.textMuted}>
428-
context: {(() => {
429-
const total = (props.currentAgent?.telemetry.tokensIn ?? 0) + (props.currentAgent?.telemetry.tokensOut ?? 0)
430-
if (total >= 1000000) return `${(total / 1000000).toFixed(1)}M`
431-
if (total >= 1000) return `${(total / 1000).toFixed(1)}K`
432-
return `${total}`
428+
{(() => {
429+
const formatTokens = (total: number) => {
430+
if (total >= 1000000) return `${(total / 1000000).toFixed(1)}M`
431+
if (total >= 1000) return `${(total / 1000).toFixed(1)}K`
432+
return `${total}`
433+
}
434+
const controllerTotal = (props.controllerState?.telemetry?.tokensIn ?? 0) + (props.controllerState?.telemetry?.tokensOut ?? 0)
435+
const stepTotal = (props.currentAgent?.telemetry.tokensIn ?? 0) + (props.currentAgent?.telemetry.tokensOut ?? 0)
436+
437+
// Controller view mode - show only controller
438+
if (isControllerViewMode()) {
439+
return `controller: ${formatTokens(controllerTotal)}`
440+
}
441+
// Workflow has controller configured - show both
442+
if (props.controllerState != null) {
443+
return `controller: ${formatTokens(controllerTotal)} │ step: ${formatTokens(stepTotal)}`
444+
}
445+
// No controller - show step only
446+
return `context: ${formatTokens(stepTotal)}`
433447
})()}
434448
</text>
435449
</box>

src/cli/tui/routes/workflow/context/ui-state/actions/workflow-actions.ts

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import type { WorkflowState, WorkflowStatus, WorkflowView, LoopState, ChainedState, InputState, TriggeredAgentState, ControllerState,
88
AutonomousMode, AgentTelemetry, AgentStatus } from "../types"
99
import { debug } from "../../../../../../../shared/logging/logger.js"
10+
import { updateControllerTelemetry as updateControllerTelemetryUtil } from "../../../state/utils"
1011

1112
export type WorkflowActionsContext = {
1213
getState(): WorkflowState
@@ -159,32 +160,9 @@ export function createWorkflowActions(ctx: WorkflowActionsContext) {
159160
const state = ctx.getState()
160161
if (!state.controllerState) return
161162

162-
const current = state.controllerState.telemetry
163-
// Context = input (uncached) + cached tokens
164-
const currentContext = (telemetry.tokensIn ?? 0) + (telemetry.cached ?? 0)
165-
// Replace telemetry values (like step agent) - shows current context, not accumulated
166-
const newTokensIn = currentContext > 0 ? currentContext : current.tokensIn
167-
const newTokensOut = telemetry.tokensOut ?? current.tokensOut
168-
const newCached = telemetry.cached ?? current.cached
169-
// Cost accumulates
170-
const newCost = (current.cost ?? 0) + (telemetry.cost ?? 0) || undefined
171-
172-
debug('[TELEMETRY:5-ACTIONS] [CONTROLLER] updateControllerTelemetry')
173-
debug('[TELEMETRY:5-ACTIONS] [CONTROLLER] INPUT: uncached=%d + cached=%d = context=%d, output=%d',
174-
telemetry.tokensIn ?? 0, telemetry.cached ?? 0, currentContext, telemetry.tokensOut ?? 0)
175-
debug('[TELEMETRY:5-ACTIONS] [CONTROLLER] RESULT: context=%d, output=%d (REPLACES previous)', newTokensIn, newTokensOut)
176-
177163
ctx.setState({
178164
...state,
179-
controllerState: {
180-
...state.controllerState,
181-
telemetry: {
182-
tokensIn: newTokensIn,
183-
tokensOut: newTokensOut,
184-
cached: newCached,
185-
cost: newCost,
186-
},
187-
},
165+
controllerState: updateControllerTelemetryUtil(state.controllerState, telemetry),
188166
})
189167
ctx.notify()
190168
}

src/cli/tui/routes/workflow/state/utils.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { AgentState, AgentStatus, AgentTelemetry } from "./types"
1+
import type { AgentState, AgentStatus, AgentTelemetry, ControllerState } from "./types"
22
import { debug } from "../../../../../shared/logging/logger.js"
33

44
export function updateAgentTelemetryInList(
@@ -53,3 +53,35 @@ export function updateAgentStatusInList(
5353
: agent,
5454
)
5555
}
56+
57+
export function updateControllerTelemetry(
58+
controller: ControllerState,
59+
telemetry: Partial<AgentTelemetry>
60+
): ControllerState {
61+
// Context = input tokens (total)
62+
// Note: cached tokens are already INCLUDED in tokensIn, not separate
63+
// cached is just metadata showing how many of those tokens were served from cache
64+
const currentContext = telemetry.tokensIn ?? 0
65+
const newTokensIn = currentContext > 0 ? currentContext : controller.telemetry.tokensIn
66+
const newTokensOut = telemetry.tokensOut ?? controller.telemetry.tokensOut
67+
const newCached = telemetry.cached ?? controller.telemetry.cached
68+
// Cost accumulates
69+
const newCost = (controller.telemetry.cost ?? 0) + (telemetry.cost ?? 0) || undefined
70+
71+
debug('[TELEMETRY:5-UTILS] [CONTROLLER] updateControllerTelemetry')
72+
debug('[TELEMETRY:5-UTILS] [CONTROLLER] INPUT: context=%d (cached=%d), output=%d',
73+
telemetry.tokensIn ?? 0, telemetry.cached ?? 0, telemetry.tokensOut ?? 0)
74+
debug('[TELEMETRY:5-UTILS] [CONTROLLER] RESULT: context=%d, output=%d (REPLACES previous)',
75+
newTokensIn, newTokensOut)
76+
77+
return {
78+
...controller,
79+
telemetry: {
80+
tokensIn: newTokensIn,
81+
tokensOut: newTokensOut,
82+
cached: newCached,
83+
cost: newCost,
84+
duration: telemetry.duration ?? controller.telemetry.duration,
85+
},
86+
}
87+
}

src/workflows/controller/view.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { formatUserInput } from '../../shared/formatters/outputMarkers.js';
2020
import { controllerPrefixUser } from '../../shared/prompts/index.js';
2121
import { AgentLoggerService, AgentMonitorService } from '../../agents/monitoring/index.js';
2222
import { executeAgent } from '../../agents/runner/runner.js';
23+
import { createTelemetryCallback } from '../../agents/execution/telemetry.js';
2324

2425
export interface ControllerViewOptions {
2526
cwd: string;
@@ -172,6 +173,11 @@ export async function runControllerView(
172173
resumeMonitoringId: controllerConfig.monitoringId,
173174
engine: controllerConfig.engine,
174175
model: controllerConfig.model,
176+
onTelemetry: createTelemetryCallback({
177+
uniqueAgentId: '',
178+
emitter,
179+
isController: true,
180+
}),
175181
});
176182

177183
// After response, go back to awaiting input
@@ -368,6 +374,11 @@ export async function runControllerView(
368374
resumeMonitoringId: controllerConfig.monitoringId,
369375
engine: controllerConfig.engine,
370376
model: controllerConfig.model,
377+
onTelemetry: createTelemetryCallback({
378+
uniqueAgentId: '',
379+
emitter,
380+
isController: true,
381+
}),
371382
});
372383

373384
// After response, go back to awaiting input

0 commit comments

Comments
 (0)