import { useAgent } from "agents/react"; import { useState } from "react"; import { createRoot } from "react-dom/client"; import type { AgentState, MyAgent } from "./server"; /** * The OpenAI Agents SDK exports SerializedRunState (a Zod schema) from * @openai/agents-core, but its inferred type is deeply nested with dozens * of fields. This interface covers only the subset the UI actually reads. */ interface RunResultState { currentAgent?: { name: string }; originalInput?: string; currentStep?: { type: string; output?: string; data?: { interruptions?: ToolApprovalItem[] }; }; lastProcessedResponse?: { toolsUsed?: string[] }; generatedItems?: unknown[]; } // Types for the agent state structure interface ToolApprovalItem { type: "tool_approval_item"; rawItem: { type: "function_call"; id: string; callId: string; name: string; status: string; arguments: string; providerData: { id: string; type: "function_call"; }; }; agent: { name: string; }; } // Modal component for tool approval function ApprovalModal({ interruption, onApprove, onReject }: { interruption: ToolApprovalItem; onApprove: () => void; onReject: () => void; }) { let args: unknown; try { args = JSON.parse(interruption.rawItem.arguments); } catch { args = { raw: interruption.rawItem.arguments }; } return (

Tool Approval Required

Tool: {interruption.rawItem.name}
Arguments:
            {JSON.stringify(args, null, 2)}
          
Agent: {interruption.agent.name}
); } // Component to display agent state function AgentStateDisplay({ state }: { state: RunResultState }) { const hasInterruption = state.currentStep?.type === "next_step_interruption"; const firstInterruption = hasInterruption ? state.currentStep?.data?.interruptions?.[0] : null; return (

Agent State

Current Agent: {state.currentAgent?.name || "Unknown"}
Original Input: {state.originalInput || "None"}
Current Step: {state.currentStep?.type || "Unknown"}
{state.currentStep?.type === "next_step_final_output" && state.currentStep.output && (
Output:
{state.currentStep.output}
)} {(state.lastProcessedResponse?.toolsUsed?.length ?? 0) > 0 && (
Tools Used:{" "} {state.lastProcessedResponse?.toolsUsed?.join(", ")}
)} {(state.generatedItems?.length ?? 0) > 0 && (
Generated Items:
              {JSON.stringify(state.generatedItems, null, 2)}
            
)} {hasInterruption && firstInterruption && (
⚠️ Tool Approval Required
Tool: {firstInterruption.rawItem.name}
Arguments: {firstInterruption.rawItem.arguments}
)}
); } function App() { const [state, setState] = useState(null); const [question, setQuestion] = useState(null); const [showApprovalModal, setShowApprovalModal] = useState(false); const [currentInterruption, setCurrentInterruption] = useState(null); console.log("[Client] App component rendered, current state:", state); console.log("[Client] Current question:", question); const agent = useAgent({ agent: "my-agent", name: "weather-chat", onStateUpdate({ serialisedRunState }: { serialisedRunState: string | null; }) { console.log("[Client] onStateUpdate called with serialisedRunState:"); if (serialisedRunState) { let parsedState: RunResultState; try { parsedState = JSON.parse(serialisedRunState) as RunResultState; } catch { console.error("[Client] Failed to parse serialisedRunState"); setState(null); return; } console.log("[Client] Parsed state:", parsedState); setState(parsedState); // Check for interruptions if (parsedState?.currentStep?.type === "next_step_interruption") { const interruption = parsedState.currentStep.data?.interruptions?.[0]; if (interruption) { console.log("[Client] Found interruption:", interruption); setCurrentInterruption(interruption); setShowApprovalModal(true); } } else if (showApprovalModal) { // Clear modal if state no longer has an interruption setShowApprovalModal(false); setCurrentInterruption(null); } } else { console.log("[Client] No serialisedRunState provided, clearing state"); setState(null); setShowApprovalModal(false); setCurrentInterruption(null); } } }); const handleAsk = () => { if (question) { console.log("[Client] Sending question to agent:", question); agent.stub.ask(question); } else { console.log("[Client] Attempted to ask empty question"); } }; const handleQuestionChange = (e: React.ChangeEvent) => { const newQuestion = e.target.value; console.log("[Client] Question input changed:", newQuestion); setQuestion(newQuestion); }; const handleApprove = () => { if (currentInterruption) { console.log( "[Client] Approving tool call:", currentInterruption.rawItem.callId ); agent.stub.proceed(currentInterruption.rawItem.callId, true); setShowApprovalModal(false); setCurrentInterruption(null); } }; const handleReject = () => { if (currentInterruption) { console.log( "[Client] Rejecting tool call:", currentInterruption.rawItem.callId ); agent.stub.proceed(currentInterruption.rawItem.callId, false); setShowApprovalModal(false); setCurrentInterruption(null); } }; // const handleCloseModal = () => { // setShowApprovalModal(false); // setCurrentInterruption(null); // }; return (

Weather Chat Agent

{state && } {showApprovalModal && currentInterruption && ( )}
); } console.log("[Client] Initializing React app"); const root = createRoot(document.getElementById("root")!); root.render();