diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index eb382baba95..3a405b778a4 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -89,6 +89,7 @@ import { createSessionRows, messageBoundaryIDs, resolvePart, + turnDuration, type CacheUsage, type PartRef, type SessionRow, @@ -1601,6 +1602,7 @@ function SessionGroupView(props: { function AssistantFooter(props: { message: SessionMessageAssistant }) { const ctx = use() + const data = useData() const local = useLocal() const dimensions = useTerminalDimensions() const theme = useTheme("elevated") @@ -1611,9 +1613,7 @@ function AssistantFooter(props: { message: SessionMessageAssistant }) { .find((model) => model.providerID === props.message.model.providerID && model.id === props.message.model.id) ?.name ?? `${props.message.model.providerID}/${props.message.model.id}`, ) - const duration = createMemo(() => - props.message.time.completed ? props.message.time.completed - props.message.time.created : 0, - ) + const duration = createMemo(() => turnDuration(props.message, data.session.message.list(ctx.sessionID))) const interrupted = createMemo(() => props.message.error?.message === "Step interrupted") return ( <> diff --git a/packages/tui/src/routes/session/rows.ts b/packages/tui/src/routes/session/rows.ts index 8540c23be17..73f4ef8d764 100644 --- a/packages/tui/src/routes/session/rows.ts +++ b/packages/tui/src/routes/session/rows.ts @@ -348,6 +348,15 @@ export function cacheReuseDrop(previous: CacheUsage | undefined, current: CacheU return drop > 0 ? drop : undefined } +export function turnDuration(message: SessionMessageAssistant, messages: SessionMessageInfo[]) { + if (message.time.completed === undefined) return 0 + const index = messages.findIndex((item) => item.id === message.id) + const input = messages + .slice(0, index === -1 ? messages.length : index) + .findLast((item) => item.type === "user" || item.type === "synthetic") + return Math.max(0, message.time.completed - (input?.time.created ?? message.time.created)) +} + function hasTokenUsage( message: SessionMessageAssistant, ): message is SessionMessageAssistant & { tokens: NonNullable } { diff --git a/packages/tui/test/cli/tui/session-rows.test.ts b/packages/tui/test/cli/tui/session-rows.test.ts index 4e7f87152db..74fcf08e8ac 100644 --- a/packages/tui/test/cli/tui/session-rows.test.ts +++ b/packages/tui/test/cli/tui/session-rows.test.ts @@ -1,6 +1,20 @@ import { expect, test } from "bun:test" import type { SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client" -import { cacheReuseDrop, messageBoundaryIDs, reduceSessionRows } from "../../../src/routes/session/rows" +import { cacheReuseDrop, messageBoundaryIDs, reduceSessionRows, turnDuration } from "../../../src/routes/session/rows" + +test("measures turn duration from the user prompt across assistant steps", () => { + const first = assistant("assistant-1", []) + first.time = { created: 8_000, completed: 11_000 } + const final = assistant("assistant-2", []) + final.time = { created: 27_000, completed: 30_000 } + const messages: SessionMessageInfo[] = [ + { type: "user", id: "user-1", text: "Question", time: { created: 1_000 } }, + first, + final, + ] + + expect(turnDuration(final, messages)).toBe(29_000) +}) test("filters OpenAI cache quantization from cache reuse drops", () => { const openai = { id: "gpt", providerID: "openai" }