Skip to content

OpenAI Agents Converter

When using the OpenAI Agents SDK (Python), the message history uses a different format than react-sse-chat. The convertOpenAIAgentsMessages utility converts raw agent items into Message[] so you can pass them directly to initialMessages.

import {
useChat,
convertOpenAIAgentsMessages,
} from "@devscalelabs/react-sse-chat";
function Chat({ agentData }: { agentData: unknown[] }) {
const { messages, sendMessage } = useChat({
api: "/chat",
initialMessages: convertOpenAIAgentsMessages(agentData),
});
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
<strong>{msg.role}:</strong>
{msg.parts.map((part, i) => {
switch (part.type) {
case "text":
return <p key={i}>{part.text}</p>;
case "tool_call":
return (
<pre key={i}>
Called {part.tool_name}({part.argument})
</pre>
);
case "tool_result":
return <pre key={i}>Result: {part.output}</pre>;
default:
return null;
}
})}
</div>
))}
</div>
);
}

By default, reasoning items are excluded. Pass { includeReasoning: true } to include them:

const messages = convertOpenAIAgentsMessages(agentData, {
includeReasoning: true,
});

Then render the reasoning parts in your component:

{msg.parts.map((part, i) => {
switch (part.type) {
case "text":
return <p key={i}>{part.text}</p>;
case "reasoning":
return (
<details key={i}>
<summary>Reasoning</summary>
<p>{part.text}</p>
</details>
);
case "tool_call":
return (
<pre key={i}>
Called {part.tool_name}({part.argument})
</pre>
);
case "tool_result":
return <pre key={i}>Result: {part.output}</pre>;
default:
return null;
}
})}

The converter recognizes the following items from the OpenAI Agents SDK:

Raw ItemConverts ToIncluded By Default
{ role: "user", content: "..." }TextPart in a new user MessageYes
{ type: "message", role: "assistant", content: [{ type: "output_text" }] }TextPart in current assistant MessageYes
{ type: "function_call", name, arguments, call_id }ToolCallPart in current assistant MessageYes
{ type: "function_call_output", call_id, output }ToolResultPart in current assistant MessageYes
{ type: "reasoning", content: [{ type: "reasoning_text" }] }ReasoningPart in current assistant MessageNo (includeReasoning: true)

Consecutive assistant-side items (reasoning, text output, tool calls, tool results) are merged into a single Message with multiple parts. A user message always creates a new message boundary.

Unknown items are silently skipped.

A common pattern is fetching the agent message history from your backend, then converting it:

import { useQuery } from "@tanstack/react-query";
import { useChat, convertOpenAIAgentsMessages } from "@devscalelabs/react-sse-chat";
function Chat({ sessionId }: { sessionId: string }) {
const { data, isLoading: isHistoryLoading } = useQuery({
queryKey: ["chat-history", sessionId],
queryFn: async () => {
const res = await fetch(`/api/history/${sessionId}`);
return res.json();
},
});
if (isHistoryLoading) return <div>Loading chat history...</div>;
return <ChatUI agentData={data.messages} />;
}
function ChatUI({ agentData }: { agentData: unknown[] }) {
const { messages, sendMessage, isLoading, stop } = useChat({
api: "/chat",
initialMessages: convertOpenAIAgentsMessages(agentData),
});
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
<strong>{msg.role}:</strong>
{msg.parts.map((part, i) => {
switch (part.type) {
case "text":
return <p key={i}>{part.text}</p>;
default:
return null;
}
})}
</div>
))}
{isLoading && <button onClick={stop}>Stop</button>}
<form
onSubmit={(e) => {
e.preventDefault();
const input = e.currentTarget.elements.namedItem("message") as HTMLInputElement;
sendMessage(input.value);
input.value = "";
}}
>
<input name="message" placeholder="Type a message..." />
<button type="submit" disabled={isLoading}>Send</button>
</form>
</div>
);
}

The converter produces parts typed as OpenAIAgentsContentPart, a union of all possible parts:

import type {
TextPart,
ToolCallPart,
ReasoningPart,
ToolResultPart,
OpenAIAgentsContentPart,
} from "@devscalelabs/react-sse-chat";

All types are individually exported, so you can reference them directly in your component props or switch statements.