Skip to content

feat(tui): live per-message response timing - #20220

Closed
want2sleeep wants to merge 2 commits into
anomalyco:devfrom
want2sleeep:feat/message-timing
Closed

feat(tui): live per-message response timing#20220
want2sleeep wants to merge 2 commits into
anomalyco:devfrom
want2sleeep:feat/message-timing

Conversation

@want2sleeep

@want2sleeep want2sleeep commented Mar 31, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #10739

Type of change

  • New feature

What does this PR do?

Add live elapsed time display for each assistant message during streaming. The timer shows end-to-end duration from when the user sent the message to when the assistant finishes responding.

Key behaviors:

  • Real-time updates every second during streaming
  • Hidden during first second to avoid millisecond flash
  • Format: 1s, 2s, 59s, 1m, 1m 30s, 2h 15m, 1d 3h
  • Stops updating once the message completes (including tool-calls finish)

How did you verify your code works?

Ran bun dev locally, sent messages and observed:

  • Timer starts at 1s and increments during streaming
  • No 0s flash for sub-second completions
  • Stops updating after completion
  • Handles hour/day durations correctly

Screenshots / recordings

Before:
before

After:
after

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Note: e2e test failures are in packages/app (web UI) and are pre-existing on dev. This PR only touches packages/opencode (TUI).

Copilot AI review requested due to automatic review settings March 31, 2026 07:44
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Mar 31, 2026
@want2sleeep want2sleeep changed the title feat(tui): live per-message response timing & git branch display in sidebar feat(tui): live per-message response timing Mar 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a live, per-assistant-message elapsed-time display in the TUI session view during streaming, intended to show end-to-end latency from the parent user message to assistant completion.

Changes:

  • Introduces a 1s ticking “now” signal during non-final assistant messages to compute live elapsed time.
  • Replaces Locale.duration(...) with a custom duration formatter for the assistant message header.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (!props.message.time.completed) return 0
const [now, setNow] = createSignal(Date.now())
createEffect(() => {
if (final()) return

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The streaming timer interval is started whenever final() is false, but messages with finish: "tool-calls" / "unknown" are intentionally non-final and still get time.completed set (e.g. tool execution). In that case this interval will run indefinitely and keep updating now even though the elapsed time is already fixed. Consider gating the interval on props.message.time.completed (or equivalent streaming state) rather than final(), so it stops once the assistant message has a completion timestamp.

Suggested change
if (final()) return
const completed = props.message.time?.completed
if (final() || completed) return

Copilot uses AI. Check for mistakes.
Comment on lines +1349 to +1353
const total = Math.floor(ms / 1000)
if (total < 60) return `${total}s`
const m = Math.floor(total / 60)
const s = total % 60
return `${m}m ${s}s`

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

durationText() formatting can display surprising values: (1) if the assistant completes in <1s, duration() becomes a small non-zero ms value and the UI will show 0s; (2) when the seconds remainder is 0 it renders "{m}m 0s" instead of "{m}m" as described in the PR; (3) durations >= 1 hour lose the existing hour/day formatting previously provided by Locale.duration. Consider adjusting the formatting/hide-threshold so sub-second completions don’t show 0s, omit 0s when s === 0, and keep hour/day support (either by extending this formatter or reusing Locale.duration with rounding).

Suggested change
const total = Math.floor(ms / 1000)
if (total < 60) return `${total}s`
const m = Math.floor(total / 60)
const s = total % 60
return `${m}m ${s}s`
const totalSeconds = Math.floor(ms / 1000)
// Hide sub-second durations instead of showing "0s"
if (totalSeconds === 0) return ""
if (totalSeconds < 60) {
return `${totalSeconds}s`
}
const totalMinutes = Math.floor(totalSeconds / 60)
const seconds = totalSeconds % 60
// Less than 1 hour: show minutes and (optionally) seconds
if (totalMinutes < 60) {
if (seconds === 0) return `${totalMinutes}m`
return `${totalMinutes}m ${seconds}s`
}
const totalHours = Math.floor(totalMinutes / 60)
const minutes = totalMinutes % 60
// Less than 1 day: show hours and (optionally) minutes
if (totalHours < 24) {
if (minutes === 0) return `${totalHours}h`
return `${totalHours}h ${minutes}m`
}
const days = Math.floor(totalHours / 24)
const hours = totalHours % 24
// 1 day or more: show days and (optionally) hours
if (hours === 0) return `${days}d`
return `${days}d ${hours}h`

Copilot uses AI. Check for mistakes.
Comment on lines +1334 to +1338
const elapsed = createMemo(() => {
const user = messages().find((x) => x.role === "user" && x.id === props.message.parentID)
if (!user || !user.time) return 0
return props.message.time.completed - user.time.created
const end = props.message.time.completed ?? now()
return end - user.time.created

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

elapsed() recomputes by scanning the full messages() array to find the parent user message. While streaming, this memo will update every second; with long sessions this becomes a repeated O(n) search per tick. Consider caching the parent user created timestamp (e.g., memoize the parent message lookup once per assistant message, or index messages by id) so timer ticks don’t repeatedly traverse the full message list.

Copilot uses AI. Check for mistakes.
Comment on lines 1411 to 1415
<span style={{ fg: theme.text }}>{Locale.titlecase(props.message.mode)}</span>
<span style={{ fg: theme.textMuted }}> · {props.message.modelID}</span>
<Show when={duration()}>
<span style={{ fg: theme.textMuted }}> · {Locale.duration(duration())}</span>
<span style={{ fg: theme.textMuted }}> · {durationText()}</span>
</Show>

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions adding git branch display in the sidebar footer, but the current change set shown here only updates per-message timing in session/index.tsx. If the sidebar footer work is intended to be part of this PR, it looks like the related file changes may be missing from the branch/commit set (or from the PR).

Copilot uses AI. Check for mistakes.
@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Mar 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@want2sleeep

Copy link
Copy Markdown
Author

Note: The e2e test failures are in packages/app (web UI) and are pre-existing on the dev branch. This PR only touches packages/opencode (TUI). Unit tests and typecheck pass.

@want2sleeep

Copy link
Copy Markdown
Author

Before / After

Before:
before

After:
after

  • Before: No elapsed time shown during streaming
  • After: Live timer updates every second (1s, 2s, 59s, 1m, 1m 30s...)

@rekram1-node

Copy link
Copy Markdown
Collaborator

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

@edso404

edso404 commented Jun 17, 2026

Copy link
Copy Markdown

Why hasn't this PR been merged? This requirement is highly discussed in the issue?
But I just want to increase the real-time jumping token count like Claude

@edso404

edso404 commented Jun 17, 2026

Copy link
Copy Markdown

Please have the maintainer review this PR again

axisrow added a commit to axisrow/opencode that referenced this pull request Aug 1, 2026
Port anomalyco#20220, closed by an automated PR-cleanup bot
without review. Closes #2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Show a live running timer for prompts in the TUI

4 participants