skills/hhopkins95/ai-systems/React Integration

React Integration

SKILL.md

React Integration

Overview

The @hhopkins/agent-runtime-react package provides React hooks and context for connecting to the agent runtime backend. It handles:

  • WebSocket connection management
  • Session lifecycle
  • Real-time streaming updates
  • State management via Context + Reducer

Installation

pnpm add @hhopkins/agent-runtime-react

Provider Setup

Wrap the application with AgentServiceProvider:

import { AgentServiceProvider } from "@hhopkins/agent-runtime-react";

function App() {
  return (
    <AgentServiceProvider
      apiUrl="http://localhost:3001"      // REST API URL
      wsUrl="http://localhost:3001"       // WebSocket URL (same server)
      apiKey="your-api-key"               // API key for auth
      debug={false}                       // Enable debug logging
    >
      <YourApp />
    </AgentServiceProvider>
  );
}

The provider:

  • Initializes REST client and WebSocket manager
  • Connects WebSocket immediately
  • Loads initial session list
  • Sets up event listeners for all WebSocket events

Hooks

useAgentSession

Manage session lifecycle - create, load, destroy sessions:

import { useAgentSession } from "@hhopkins/agent-runtime-react";

function SessionManager() {
  const {
    session,           // Current session state (null if not loaded)
    runtime,           // Runtime state (sandbox status)
    isLoading,         // Operation in progress
    error,             // Last error
    createSession,     // Create new session
    loadSession,       // Load existing session
    destroySession,    // Destroy current session
    syncSession,       // Manually sync to persistence
    updateSessionOptions,  // Update session options
  } = useAgentSession(sessionId);  // Optional: auto-load on mount

  // Create a new session
  const handleCreate = async () => {
    const newSessionId = await createSession(
      "agent-profile-id",     // Agent profile reference
      "claude-agent-sdk",     // Architecture type
      { model: "sonnet" }     // Optional session options
    );
  };

  return (
    <div>
      <p>Sandbox: {runtime?.sandbox.status}</p>
      <button onClick={handleCreate}>New Session</button>
    </div>
  );
}

Important: Call useAgentSession at the page/container level to ensure WebSocket room is joined regardless of which child components render.

useMessages

Access conversation blocks and send messages:

import { useMessages } from "@hhopkins/agent-runtime-react";

function Chat({ sessionId }: { sessionId: string }) {
  const {
    blocks,              // ConversationBlock[] - pre-merged with streaming
    streamingBlockIds,   // Set<string> - IDs currently streaming
    isStreaming,         // boolean - any block streaming
    metadata,            // Token/cost info
    error,               // Last error
    sendMessage,         // Send message to agent
    getBlock,            // Get block by ID
    getBlocksByType,     // Filter blocks by type
  } = useMessages(sessionId);

  const handleSend = async (text: string) => {
    await sendMessage(text);
    // Response arrives via WebSocket events
  };

  return (
    <div>
      {blocks.map((block) => (
        <BlockRenderer
          key={block.id}
          block={block}
          isStreaming={streamingBlockIds.has(block.id)}
        />
      ))}
      <MessageInput onSend={handleSend} disabled={isStreaming} />
    </div>
  );
}

Streaming behavior: Blocks are pre-merged with streaming content. A temporary block with ID "streaming" appears during active streaming.

useSessionList

List all sessions:

import { useSessionList } from "@hhopkins/agent-runtime-react";

function SessionList({ onSelect }: { onSelect: (id: string) => void }) {
  const { sessions, isLoading, error, refresh } = useSessionList();

  return (
    <ul>
      {sessions.map((session) => (
        <li key={session.sessionId} onClick={() => onSelect(session.sessionId)}>
          {session.sessionId} - {session.type}
        </li>
      ))}
    </ul>
  );
}

useWorkspaceFiles

Track files modified by the agent:

import { useWorkspaceFiles } from "@hhopkins/agent-runtime-react";

function FileExplorer({ sessionId }: { sessionId: string }) {
  const { files, getFile } = useWorkspaceFiles(sessionId);

  return (
    <ul>
      {files.map((file) => (
        <li key={file.path}>
          {file.path}
          <pre>{file.content}</pre>
        </li>
      ))}
    </ul>
  );
}

useSubagents

Track subagent transcripts:

import { useSubagents } from "@hhopkins/agent-runtime-react";

function SubagentViewer({ sessionId }: { sessionId: string }) {
  const { subagents, getSubagent } = useSubagents(sessionId);

  return (
    <div>
      {subagents.map((subagent) => (
        <div key={subagent.id}>
          <h4>{subagent.name}</h4>
          <p>Status: {subagent.status}</p>
        </div>
      ))}
    </div>
  );
}

useEvents

Access debug event log for monitoring WebSocket events:

import { useEvents } from "@hhopkins/agent-runtime-react";

function DebugPanel() {
  const { events, clearEvents } = useEvents();

  return (
    <div>
      <button onClick={clearEvents}>Clear</button>
      {events.map((event, i) => (
        <pre key={i}>{JSON.stringify(event, null, 2)}</pre>
      ))}
    </div>
  );
}

Rendering Blocks

Handle different block types when rendering:

function BlockRenderer({ block, isStreaming }) {
  switch (block.type) {
    case "user_message":
      return <UserMessage content={block.content} />;

    case "assistant_text":
      return (
        <AssistantMessage
          content={block.content}
          isStreaming={isStreaming}
        />
      );

    case "tool_use":
      return (
        <ToolCall
          name={block.toolName}
          input={block.input}
        />
      );

    case "tool_result":
      return (
        <ToolResult
          content={block.content}
          isError={block.isError}
        />
      );

    case "thinking":
      return <ThinkingBlock content={block.content} />;

    case "subagent":
      return (
        <SubagentCall
          name={block.name}
          status={block.status}
        />
      );

    case "error":
      return <ErrorMessage message={block.message} />;

    default:
      return null;
  }
}

Streaming Patterns

Show typing indicator

function Chat({ sessionId }) {
  const { blocks, isStreaming } = useMessages(sessionId);

  return (
    <div>
      {blocks.map((block) => <BlockRenderer key={block.id} block={block} />)}
      {isStreaming && <TypingIndicator />}
    </div>
  );
}

Animated text streaming

function AssistantMessage({ content, isStreaming }) {
  return (
    <div className={isStreaming ? "streaming" : ""}>
      {content}
      {isStreaming && <span className="cursor">|</span>}
    </div>
  );
}

Optimistic updates

User messages appear immediately via optimistic updates. The hook dispatches OPTIMISTIC_USER_MESSAGE, then replaces it with the real message when block_complete arrives.

Error Handling

Errors are surfaced in multiple ways:

function Chat({ sessionId }) {
  const { error: sessionError } = useAgentSession(sessionId);
  const { error: messageError, blocks } = useMessages(sessionId);

  // Check hook-level errors
  if (sessionError) return <Error message={sessionError.message} />;

  // ErrorBlocks appear inline in the conversation
  const errorBlocks = blocks.filter((b) => b.type === "error");

  return <div>...</div>;
}

Related Skills

  • overview - Understanding the runtime architecture
  • backend-setup - Setting up the backend server
  • agent-design - Configuring agent profiles
Weekly Installs
0
First Seen
Jan 1, 1970