oc-metricboard 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assistant-progress.ts +2 -0
- package/src/collector.ts +9 -1
- package/src/session-hydration.ts +5 -0
- package/src/turn-state.ts +11 -0
- package/src/types.ts +1 -0
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import { currentRequest } from "./request-state"
|
|
|
4
4
|
import { eventAggregateID, eventID, eventProperties, stringEventProperty } from "./event-bus"
|
|
5
5
|
import type { EventHandlerContext } from "./collector-state"
|
|
6
6
|
import { recordLiveTokens } from "./live-speed"
|
|
7
|
+
import { ensureTurn, recordTurnFirstToken } from "./turn-state"
|
|
7
8
|
|
|
8
9
|
export interface AssistantProgressPart {
|
|
9
10
|
readonly partID: string
|
|
@@ -26,6 +27,7 @@ function applyTokenDelta(
|
|
|
26
27
|
if (state.turns.get(sessionID)?.finalizedSteps.has(messageID)) return
|
|
27
28
|
const current = currentRequest({ state, actions, sessionID, messageID, now })
|
|
28
29
|
if (current.firstTokenTime === null) current.firstTokenTime = now
|
|
30
|
+
recordTurnFirstToken(ensureTurn(state.turns, sessionID, now), now)
|
|
29
31
|
if (deltaTokens > 0) {
|
|
30
32
|
current.estimatedOutputTokens += deltaTokens
|
|
31
33
|
current.lastDeltaTime = now
|
package/src/collector.ts
CHANGED
|
@@ -546,7 +546,15 @@ export function createCollector(
|
|
|
546
546
|
|
|
547
547
|
// NEW: Build per-model breakdown for tree scope
|
|
548
548
|
const modelBreakdown = scope === "tree" ? aggregateByModel(ids, now, scope) : []
|
|
549
|
-
|
|
549
|
+
// Turn-level TTFT: opencode stamps step.started at the first token, so
|
|
550
|
+
// intra-turn steps cannot measure their own request start (they would
|
|
551
|
+
// collapse to ~0). Anchor TTFT to the turn's user message instead —
|
|
552
|
+
// stable across all steps within a turn, refreshed on each new turn.
|
|
553
|
+
// Falls back to the per-request measurement when no turn timing exists.
|
|
554
|
+
const turnTtft = foregroundTurn?.firstTokenTime != null
|
|
555
|
+
? gateTtft(foregroundTurn.firstTokenTime - foregroundTurn.turnStartTime)
|
|
556
|
+
: null
|
|
557
|
+
const ttft = turnTtft ?? (foregroundRequest ? getTtft(foregroundRequest) : null)
|
|
550
558
|
|
|
551
559
|
const result: MetricsAggregate = {
|
|
552
560
|
sessionIDs: contributingSessionIDs.length > 0 ? contributingSessionIDs : [rootID],
|
package/src/session-hydration.ts
CHANGED
|
@@ -233,6 +233,11 @@ export async function hydrateSession(input: HydrateSessionInput): Promise<boolea
|
|
|
233
233
|
input.now,
|
|
234
234
|
) ?? requestStartTime
|
|
235
235
|
const turn = createTurnMetrics(input.sessionID, turnStartTime)
|
|
236
|
+
// Turn-level TTFT anchor: the first trailing assistant's createdTime is
|
|
237
|
+
// stamped at its first token, so turnStart → that instant is the hydrated
|
|
238
|
+
// turn's time-to-first-token. Fall back to the live request's first token.
|
|
239
|
+
turn.firstTokenTime = toPerformanceTime(trailingAssistants[0]?.createdTime, input.now)
|
|
240
|
+
?? current.firstTokenTime
|
|
236
241
|
for (const step of trailingAssistants) {
|
|
237
242
|
if (!step.tokens || !hasPositiveAssistantTokens(step.tokens)) continue
|
|
238
243
|
const stepStart = toPerformanceTime(step.createdTime, input.now) ?? turnStartTime
|
package/src/turn-state.ts
CHANGED
|
@@ -5,6 +5,7 @@ export function createTurnMetrics(sessionID: string, now: number): TurnMetrics {
|
|
|
5
5
|
return {
|
|
6
6
|
sessionID,
|
|
7
7
|
turnStartTime: now,
|
|
8
|
+
firstTokenTime: null,
|
|
8
9
|
completeTime: null,
|
|
9
10
|
finalizedOutputTokens: 0,
|
|
10
11
|
finalizedSteps: new Map(),
|
|
@@ -83,3 +84,13 @@ export function completeTurn(turn: TurnMetrics, now: number): void {
|
|
|
83
84
|
turn.isComplete = true
|
|
84
85
|
turn.completeTime = now
|
|
85
86
|
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Record the turn's first token instant (once). opencode stamps step.started
|
|
90
|
+
* at the first token, so intra-turn steps cannot measure their own request
|
|
91
|
+
* start; the turn-level anchor (user message → first delta) is the only
|
|
92
|
+
* meaningful TTFT observable from the event stream.
|
|
93
|
+
*/
|
|
94
|
+
export function recordTurnFirstToken(turn: TurnMetrics, now: number): void {
|
|
95
|
+
if (turn.firstTokenTime === null) turn.firstTokenTime = now
|
|
96
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -37,6 +37,7 @@ export interface FinalizedStepUsage {
|
|
|
37
37
|
export interface TurnMetrics {
|
|
38
38
|
sessionID: string
|
|
39
39
|
turnStartTime: number
|
|
40
|
+
firstTokenTime: number | null // First token of the turn (user message → first delta)
|
|
40
41
|
completeTime: number | null
|
|
41
42
|
finalizedOutputTokens: number
|
|
42
43
|
finalizedSteps: Map<string, FinalizedStepUsage>
|