# Message Timing
URL: /docs/guides/message-timing
Display stream timing metadata like duration, tokens per second, and time to first token.
Display stream performance metrics — duration, tokens per second, TTFT — on assistant messages.
Reading Timing Data \[#reading-timing-data]
Use `useMessageTiming()` inside a message component to access timing data:
```tsx
import { useMessageTiming } from "@assistant-ui/react";
const MessageTimingDisplay: FC = () => {
const timing = useMessageTiming();
if (!timing?.totalStreamTime) return null;
const formatMs = (ms: number) =>
ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(1)}s`;
return (
{formatMs(timing.totalStreamTime)}
{timing.tokensPerSecond !== undefined &&
` · ${timing.tokensPerSecond.toFixed(1)} tok/s`}
);
};
```
Place it inside `MessagePrimitive.Root`, typically near the action bar:
```tsx {8}
const AssistantMessage: FC = () => {
return (
);
};
```
MessageTiming Fields \[#messagetiming-fields]
| Field | Type | Description |
| ----------------- | --------- | ---------------------------------- |
| `streamStartTime` | `number` | Unix timestamp when stream started |
| `firstTokenTime` | `number?` | Time to first text token (ms) |
| `totalStreamTime` | `number?` | Total stream duration (ms) |
| `tokenCount` | `number?` | Estimated or actual token count |
| `tokensPerSecond` | `number?` | Throughput (tokens/sec) |
| `totalChunks` | `number` | Total stream chunks received |
| `toolCallCount` | `number` | Number of tool calls |
Runtime Support \[#runtime-support]
| Runtime | Supported | Notes |
| ------------------------- | :-------: | -------------------------------------------- |
| DataStream | Yes | Automatic via `AssistantMessageAccumulator` |
| AI SDK (`useChatRuntime`) | Yes | Automatic via client-side tracking |
| Local (`useLocalRuntime`) | Yes | Pass timing in `ChatModelRunResult.metadata` |
| ExternalStore | Yes | Pass timing in `ThreadMessageLike.metadata` |
| LangGraph | No | Not yet implemented |
| AG-UI | No | Not yet implemented |
DataStream \[#datastream]
Timing is tracked automatically inside `AssistantMessageAccumulator`. No setup required.
```tsx
import { useDataStreamRuntime } from "@assistant-ui/react-data-stream";
const runtime = useDataStreamRuntime({ api: "/api/chat" });
// useMessageTiming() works out of the box
```
AI SDK (useChatRuntime) \[#ai-sdk-usechatruntime]
Timing is tracked automatically on the client side by observing streaming state transitions and content changes. Timing is finalized when each stream completes. `tokenCount` and `tokensPerSecond` are estimated from text length.
```tsx
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
const runtime = useChatRuntime({ api: "/api/chat" });
// useMessageTiming() works out of the box
```
Local (useLocalRuntime) \[#local-uselocalruntime]
Pass timing in the `metadata` field of your `ChatModelRunResult`:
```tsx
import type { ChatModelAdapter } from "@assistant-ui/react";
const myAdapter: ChatModelAdapter = {
async run({ messages, abortSignal }) {
const startTime = Date.now();
const result = await callMyAPI(messages, abortSignal);
const totalStreamTime = Date.now() - startTime;
return {
content: [{ type: "text", text: result.text }],
metadata: {
timing: {
streamStartTime: startTime,
totalStreamTime,
tokenCount: result.usage?.completionTokens,
tokensPerSecond:
result.usage?.completionTokens
? result.usage.completionTokens / (totalStreamTime / 1000)
: undefined,
totalChunks: 1,
toolCallCount: 0,
},
},
};
},
};
```
ExternalStore (useExternalStoreRuntime) \[#externalstore-useexternalstoreruntime]
Pass timing in the `metadata.timing` field of your `ThreadMessageLike` messages:
```tsx
import type { ThreadMessageLike } from "@assistant-ui/react";
const message: ThreadMessageLike = {
role: "assistant",
content: [{ type: "text", text: fullText }],
metadata: {
timing: {
streamStartTime: startTime,
firstTokenTime,
totalStreamTime,
tokenCount,
tokensPerSecond,
totalChunks: chunks,
toolCallCount: 0,
},
},
};
```
API Reference \[#api-reference]
useMessageTiming() \[#usemessagetiming]
```tsx
const timing: MessageTiming | undefined = useMessageTiming();
```
Returns timing metadata for the current assistant message, or `undefined` for non-assistant messages or when no timing data is available.
Must be used inside a `MessagePrimitive.Root` context.