diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 727284634..b7ff2ff3e 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -1281,22 +1281,30 @@ function AssistantMessage(props: { index: number; message: AssistantMessage; par }) const stats = createMemo(() => { - if (!final() || !props.message.time.completed) return null + // if (!final() || !props.message.time.completed) return null const list = messages() - let tokens = props.message.tokens?.output || 0 + const stats = { + duration: 0, + tps: [] as number[], + } - for (let i = props.index - 1; i >= 0; i--) { + for (let i = props.index; i >= 0; i--) { const msg = list[i] + if (msg.id === props.message.parentID && msg.role === "user") { - if (!msg.time?.created) return null + stats.duration = (props.message.time.completed ?? Date.now()) - msg.time.created return { - duration: props.message.time.completed - msg.time.created, - tokens, + duration: (props.message.time.completed ?? Date.now()) - msg.time.created, + tps: stats.tps.reduce((sum, x) => sum + x, 0) / stats.tps.length, } } if (msg.role === "assistant") { - tokens += msg.tokens?.output || 0 + if (msg.tokens.output && msg.time.started && msg.time.streamed) { + const duration = msg.time.streamed - msg.time.started + const tps = msg.tokens.output / (duration / 1000) + stats.tps.push(tps) + } } } @@ -1355,7 +1363,7 @@ function AssistantMessage(props: { index: number; message: AssistantMessage; par {" "} · {Locale.duration(s().duration)} - 0}> · {(s().tokens / (s().duration / 1000)).toFixed(1)} tok/s + 0}> · {s().tps.toFixed(0)} tok/s )} diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 178751a22..dc7acdc7b 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -392,6 +392,8 @@ export namespace MessageV2 { role: z.literal("assistant"), time: z.object({ created: z.number(), + started: z.number().optional(), + streamed: z.number().optional(), completed: z.number().optional(), }), error: z diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index e7532d200..4dfaef0fe 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -57,6 +57,8 @@ export namespace SessionProcessor { switch (value.type) { case "start": SessionStatus.set(input.sessionID, { type: "busy" }) + input.assistantMessage.time.started = Date.now() + await Session.updateMessage(input.assistantMessage) break case "reasoning-start": @@ -334,12 +336,16 @@ export namespace SessionProcessor { await Session.updatePart(currentText) } currentText = undefined + input.assistantMessage.time.streamed = Date.now() + await Session.updateMessage(input.assistantMessage) break case "finish": break default: + type: value.type, + }) log.info("unhandled", { ...value, }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 4050ef157..b7e8ddc57 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -207,6 +207,8 @@ export type AssistantMessage = { role: "assistant" time: { created: number + started?: number + streamed?: number completed?: number } error?: