Skip to content
Protocol

Event metadata

TanStack AI puts the finish reason and the model id in AG-UI metadata. useChat reads those fields from there.

Put these extras in metadata.tanstack on the event. The spec fields stay at the top level.

Note

A TanStack chat() server already writes metadata.tanstack. If your server sends AG-UI events itself, use this page.

Quick setup

1. Send a complete RUN_FINISHED event

Copy this event. Change the ids, the counts, and the model:

json
{
  "type": "RUN_FINISHED",
  "threadId": "thread-1",
  "runId": "run-1",
  "usage": [
    {
      "inputTokens": 12,
      "outputTokens": 34,
      "totalTokens": 46
    }
  ],
  "metadata": {
    "tanstack": {
      "finishReason": "stop",
      "model": "gpt-6.1-sol"
    }
  }
}

2. Read the fields on the client

Check chunk.type, then read chunk.metadata?.tanstack. You do not need a helper:

tsx
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";

export function Chat() {
  const { messages } = useChat({
    connection: fetchServerSentEvents("/api/chat"),
    threadId: "support-chat",
    onChunk: (chunk) => {
      if (chunk.type === "RUN_FINISHED") {
        console.log(chunk.usage);
        console.log(chunk.metadata?.tanstack?.finishReason);
        console.log(chunk.metadata?.tanstack?.model);
      }
    },
  });

  return <pre>{JSON.stringify(messages, null, 2)}</pre>;
}

Send a message. The console now shows the token usage, "stop", and the model id.

How it works

The client copies metadata.tanstack from the wire event onto the chunk. Spec fields such as usage stay where the spec puts them.

Send these fields on every successful run:

FieldWhereWhy the client needs it
type, threadId, runIdSpec top levelThey frame the run. Without the ids, resume and correlation break.
usage[]Spec top level on RUN_FINISHED and RUN_ERRORToken counts: inputTokens, outputTokens, totalTokens.
finishReasonmetadata.tanstack on RUN_FINISHEDAfter a tool result, the client continues only when this is not "stop".
modelmetadata.tanstackGeneration reconstruct uses it.

finishReason is one of these values: "stop", "length", "content_filter", "tool_calls", or null.

Warning

Always send finishReason after a tool result. If it is missing, the client treats it as not "stop" and can send another turn.

For the full event list, see Stream Events. For the request body, see AG-UI Client Compliance.

Advanced

Usage on the wire and in process

The wire uses the spec usage[] array with inputTokens. In process, usage is the TanStack TokenUsage type with promptTokens. The client rebuilds TokenUsage from the array.

RUN_ERROR

An AG-UI RUN_ERROR has message and an optional code at the top level. Put the correlation ids in metadata.tanstack:

json
{
  "type": "RUN_ERROR",
  "message": "Provider timeout",
  "code": "TIMEOUT",
  "metadata": {
    "tanstack": {
      "threadId": "thread-1",
      "runId": "run-1",
      "model": "gpt-6.1-sol"
    }
  }
}

For a rate limit, put the wait time in metadata.tanstack.retryAfterMs, in milliseconds. Clients read it from there. See Rate limits.

Put the provider error type in metadata.tanstack.errorType and the request id in metadata.tanstack.requestId. See Read the provider error type and request id.

Fields for other features

When you use one of these features, add its field:

  • Extra usage. Spec usage[] also accepts cachedInputTokens and reasoningTokens. Put cost and other usage fields without a spec slot in metadata.tanstack.usage. The client merges them into TokenUsage.
  • Interrupt errors. On RUN_ERROR, set metadata.tanstack.interruptErrors. Then ChatClient can match a failed interrupt submit.
  • Tool output error. On TOOL_CALL_RESULT, set metadata.tanstack.state to "output-error" when the tool result is an error payload.
  • Message timestamps. On wire messages, metadata.tanstack.createdAt is an ISO-8601 string.
  • Signatures. Stream thinking signatures on REASONING_ENCRYPTED_VALUE. In the next-turn body, set the spec encryptedValue on role: "reasoning" messages and on toolCalls. Other provider fields for a tool call go in metadata.tanstack.toolCallMetadata. See Thinking & Reasoning.