# Reasoning UI with the OpenAI Agents SDK

> Ask for reasoning summaries in modelSettings, forward reasoning_item_created events from the run stream, and show a thinking line that folds when the answer starts.

Published 2026-11-04. Facts checked 2026-11-04. Source: https://pixilui.com/guides/reasoning-ui-openai-agents-sdk

With the OpenAI Agents SDK, set `modelSettings: { reasoning: { effort: "medium", summary: "auto" } }` on the agent to get reasoning summaries back. When you stream the run, each one arrives as a `reasoning_item_created` run item event. Forward those to the browser as steps of a thinking line with a live timer, and mark it done when the first `output_text_delta` arrives, so it folds to "Thought for 9s" above the answer.

## How the Agents SDK exposes reasoning

The SDK's [Models](https://openai.github.io/openai-agents-js/guides/models/) guide lists `reasoning` in `ModelSettings`: an `effort`, and a `summary` of `auto`, `concise` or `detailed` that controls how much reasoning summary the model returns. The [Streaming](https://openai.github.io/openai-agents-js/guides/streaming/) guide lists `reasoning_item_created` among the run item events, and shows `output_text_delta` as the raw model event that carries answer text.

So the order on the wire is the order the UI needs: reasoning items while the model thinks, then text deltas once it answers. The reasoning item carries the summary text, not the model's raw thinking; show it as short steps, not a transcript.

## From events to the thinking line

| Moment | Agents SDK event | Thinking state props |
| --- | --- | --- |
| Request sent | Before the first event | `label`, `startedAt` |
| A summary arrives | `reasoning_item_created`, text in `item.rawItem.content` | A new step, `active`; the previous one `done` |
| The answer starts | `raw_model_stream_event` with `data.type === "output_text_delta"` | `done`; the timer freezes, the line folds |
| The model returns no summary | No reasoning items | `label` and timer only |
| Answer text streams | More `output_text_delta` | `StreamingText` with the text so far |

## The code

The route asks for summaries and forwards three kinds of line: a reasoning step, the answer starting, and answer text.

```ts
// app/api/think/route.ts
import { Agent, run } from "@openai/agents";

export type ThinkEvent = { type: "reasoning"; text: string } | { type: "answer"; delta: string } | { type: "end" };

const agent = new Agent({
  name: "Analyst",
  instructions: "Answer questions about the revenue data.",
  model: "gpt-5.5",
  modelSettings: { reasoning: { effort: "medium", summary: "auto" } },
});

export async function POST(req: Request) {
  const { question }: { question: string } = await req.json();
  const result = await run(agent, question, { stream: true });
  const encoder = new TextEncoder();

  const body = new ReadableStream({
    async start(controller) {
      const send = (event: ThinkEvent) => controller.enqueue(encoder.encode(`${JSON.stringify(event)}\n`));
      for await (const event of result) {
        if (event.type === "run_item_stream_event" && event.name === "reasoning_item_created" && event.item.rawItem.type === "reasoning") {
          const text = event.item.rawItem.content.map((part) => part.text).join(" ").trim();
          if (text) send({ type: "reasoning", text });
        }
        if (event.type === "raw_model_stream_event" && event.data.type === "output_text_delta") send({ type: "answer", delta: event.data.delta });
      }
      await result.completed;
      send({ type: "end" });
      controller.close();
    },
  });

  return new Response(body, { headers: { "Content-Type": "application/x-ndjson" } });
}
```

The client grows the steps, folds the line on the first answer text, and streams the answer under it.

```tsx
// components/analyst-answer.tsx
"use client";

import { useState } from "react";
import { StreamingText } from "@/components/pixilui/streaming-text";
import { ThinkingState, type ThinkingStateStep } from "@/components/pixilui/thinking-state";
import type { ThinkEvent } from "../app/api/think/route";

export function AnalystAnswer() {
  const [steps, setSteps] = useState<ThinkingStateStep[]>([]);
  const [answer, setAnswer] = useState("");
  const [streaming, setStreaming] = useState(false);
  const [startedAt, setStartedAt] = useState<number>();

  const apply = (event: ThinkEvent) => {
    if (event.type === "reasoning")
      setSteps((prev) => [...prev.map((s) => ({ ...s, status: "done" as const })), { id: String(prev.length), text: event.text.split(/(?<=[.!?])\s/)[0]!.slice(0, 90), status: "active" }]);
    if (event.type === "answer") setAnswer((so) => so + event.delta);
    if (event.type === "end") setStreaming(false);
  };

  async function ask() {
    setSteps([]);
    setAnswer("");
    setStreaming(true);
    setStartedAt(Date.now());
    const response = await fetch("/api/think", { method: "POST", body: JSON.stringify({ question: "Why did net revenue drop 6% in October?" }) });
    const reader = response.body!.pipeThrough(new TextDecoderStream()).getReader();
    let buffer = "";
    for (;;) {
      const { value, done } = await reader.read();
      if (done) break;
      const lines = (buffer + value).split("\n");
      buffer = lines.pop() ?? "";
      lines.filter(Boolean).forEach((line) => apply(JSON.parse(line) as ThinkEvent));
    }
    setStreaming(false);
  }

  return (
    <section aria-live="polite">
      <button type="button" onClick={ask} aria-disabled={streaming}>
        Explain the October drop
      </button>
      {startedAt ? <ThinkingState label="Looking into October revenue" steps={steps} startedAt={startedAt} done={answer.length > 0} /> : null}
      {answer ? <StreamingText text={answer} streaming={streaming} /> : null}
    </section>
  );
}
```

Install both with `npx shadcn@latest add @pixilui/thinking-state @pixilui/streaming-text`.

## What the thinking line adds

- **A labeled wait from the first moment**, before any summary arrives.
- **Steps that check off.** Each new summary becomes the active step; the one before it draws its check.
- **A timer that freezes and folds.** The first answer text ends the thinking; the line becomes "Thought for 9s".
- **Reduced motion.** No moving light and no rise; steps change at once.

## When the framework already ships a UI

The TypeScript Agents SDK's guides list no React components; its streaming examples print to the terminal. ChatKit, OpenAI's chat UI with React bindings, lists chain-of-thought visualizations among its features and can sit in front of an agent you host; if ChatKit renders your chat, use its reasoning display. The thinking line is for answers shown in your own screens.

## When not to use this

- **Models without reasoning.** If you set no `reasoning` or the model does not reason, show the label and timer only, or nothing for fast replies.
- **Showing the summary in full.** If people need to read the whole summary, render it as text under a disclosure, not as clipped steps.
- **Tool-heavy runs.** When the agent calls tools between thoughts, use a [run timeline](/guides/agent-run-timeline-openai-agents-sdk) for the steps.

## Questions

### How do I get reasoning summaries from the OpenAI Agents SDK?

Set `modelSettings.reasoning.summary` on the agent to `auto`, `concise` or `detailed`, with a reasoning model. Summaries then arrive as reasoning items; in a streamed run, as `reasoning_item_created` events.

### How do I know when the model stopped thinking?

The first `raw_model_stream_event` whose `data.type` is `output_text_delta` is the first token of the answer. Mark the thinking line done there; the time since you sent the request is the thinking time.

### Can I show the model's raw chain of thought?

The reasoning item in this flow carries summary text, which is what `summary` asks for. Show that, and say it is a summary if you show it in full.

## Related

- [How to show an AI model's reasoning or thinking state](/guides/show-ai-reasoning-thinking-state): what a thinking line needs, framework aside.
- [Reasoning UI with the Vercel AI SDK](/guides/reasoning-ui-vercel-ai-sdk): the same line from reasoning parts.
- [Agent run timeline with the OpenAI Agents SDK](/guides/agent-run-timeline-openai-agents-sdk): when the run is more than thinking.

## Sources

- [OpenAI Agents SDK (TypeScript): Models](https://openai.github.io/openai-agents-js/guides/models/), read 4 November 2026
- [OpenAI Agents SDK (TypeScript): Streaming](https://openai.github.io/openai-agents-js/guides/streaming/), read 4 November 2026
- [OpenAI: ChatKit](https://developers.openai.com/api/docs/guides/chatkit), read 4 November 2026
