feat(chat): persist message and tool-call timestamps#149
Conversation
b0e0252 to
528ebf5
Compare
|
Before I proceed (hence why I left it marked as a draft) - @willdady wanted to check one thing with you. My ultimate goal at first was that some tool calls / subagents are running even for 10+ minutes and I wanted to see that in timestamps. But then I accidentally deleted that "robot" icon from tool calls and I must admit that it looks far better and saves so much vertical space in chats.
What's your take on this? My two proposals are either:
But ultimately I like option number 1 much more as it deliberately saves alot of space. |
Add timestamps to chat UI: per-tool-call (next to BotIcon step separator) and per-message (next to action bar buttons). Persisted in existing JSONB chat.messages column — no schema migration. Tool start time stored in toolMetadata.startedAt; message creation time stored in metadata.createdAt. User-message timestamp is stamped client-side at send (and on edit/resend) so the optimistic UI shows it immediately; backend keeps a server-side fallback on sink.onStart. Assistant-message timestamp comes from the AI SDK's messageMetadata callback. AI SDK ai@6.0.191 quirk: the tool-output-available chunk handler ignores chunk.toolMetadata and reuses the invocation's existing toolMetadata from tool-input-available. The withToolTimestamps TransformStream therefore injects on tool-input-available. finalize moved from toUIMessageStream.onFinish to the snapshot-loop finally block so lastMessages already contains toolMetadata when the sink writes to the DB. Historical messages without createdAt render no timestamp (intentional — no fake client-side fallback). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
528ebf5 to
a8e0e00
Compare
willdady
left a comment
There was a problem hiding this comment.
Thanks for the work here, @MesoX! Having looked this over, I'm not really sold on adding message timestamps in general — I'm not seeing enough value to justify the extra UI, and they're ambiguous as they stand: they show the time only, with no date, so once a conversation is more than a day old you can't actually tell when a message was sent.
The one piece I would like to see is on the tool header. Rather than showing a wall-clock timestamp there, it'd be much more useful to show the tool's run duration (e.g. "1.2s"). The current clock time next to a tool call doesn't tell you much, whereas how long the tool took to run is genuinely informative. That'd mean also persisting the tool's completion time (right now only startedAt is captured) so the duration can be computed.
So my suggestion: drop the message-level timestamps, and repurpose the tool header timestamp into a run duration instead.
|
@willdady thanks for the info, that's not a bad idea. I will reiterate through it a bit. One more thing this solves for me is the latency until first token produced (e,g, you write a message and then you wait ~10 seconds before something even starts happening). With timestamps I can measure it to some degree, but the truth is I cann rather see it as maybe separate icon under the response from LLM where some standard info can be stored under some (i) icon - e.g. on mouse over. It could show not only the latency, but also input tokens, output tokens, number of tool calls and other information. But I will let it go through my head a bit and make a separate PR for it. But at the same time, this might only be relevant for local LLMs. |
Reworks PR willdady#149 per review: drop the message-level timestamps (low value, ambiguous time-only display) and repurpose the tool header into a run duration ("950ms" / "1.2s" / "1m 3s"). - withToolTimestamps still injects startedAt on tool-input-available, and now records completedAt per toolCallId. The AI SDK ignores toolMetadata on tool-output chunks, so the end time cannot ride the chunk; applyToolCompletions stamps it onto the built message before the sink persists it. - Frontend computes the duration from the startedAt/completedAt pair via a shared formatToolDuration helper; in-progress and pre-existing tool calls render nothing. - Removed client-side message createdAt stamping (chat.tsx, use-message-editing.ts) and the assistant messageMetadata createdAt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Summary
chat.messagescolumn — no schema migration. Tool start time stored intoolMetadata.startedAt; message creation time stored inmetadata.createdAt.sink.onStart.messageMetadatacallback (the callback fires for bothstartandfinishchunks, so callingnew Date()inside would store finish-time and mislabel it as createdAt).Technical notes
tool-output-availablechunk handler ignoreschunk.toolMetadataand reuses the invocation's existingtoolMetadatafromtool-input-available. ThewithToolTimestampsTransformStreamtherefore injects ontool-input-available.finalizemoved fromtoUIMessageStream.onFinishto the snapshot-loopfinallyblock solastMessagesalready containstoolMetadatawhen the sink writes to the DB.step-startparts (the SDK's explicit round boundary marker) rather than inferring from empty text parts.stampLastUserMessageCreatedAtreturns a newRunInput(non-mutating) so callers don't observe side effects on their request payload.createdAtrender no timestamp (intentional — no fake client-side fallback).Tests
withToolTimestamps(3 cases: injectsstartedAt, preserves existingtoolMetadata, passes other chunks through unchanged).nowargument for deterministic tests.Test plan
HH:mm:ssappears next to the small BotIcon above each tool-call card.🤖 Generated with Claude Code