CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-langchain--langgraph-sdk

TypeScript/JavaScript SDK for interacting with the LangGraph REST API server

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

react.mddocs/

React Integration

The React integration provides comprehensive hooks and UI components for building interactive LangGraph applications. It includes streaming hooks for real-time execution monitoring, UI component loading for dynamic interfaces, and complete React state management for LangGraph interactions.

Core Functionality

The React integration supports:

  • Streaming Hooks: Real-time streaming of LangGraph runs with React state management
  • UI Components: Dynamic loading of external UI components with context
  • Message Management: UI message system with reducers and type safety
  • Server Utilities: Server-side helpers for type-safe UI component rendering
  • Branch Management: Handle execution branches and history in React applications
  • Error Handling: Comprehensive error states and loading management

useStream Hook

The main React hook for streaming LangGraph runs with comprehensive state management.

function useStream<StateType = any, Bag = any>(
  options: UseStreamOptions<StateType, Bag>
): UseStream<StateType, Bag>;

interface UseStreamOptions<StateType, Bag> {
  /** LangGraph client instance */
  client: Client;
  /** Assistant ID to execute */
  assistantId: string;
  /** Thread ID (optional, will create new if not provided) */
  threadId?: string;
  /** Stream modes to enable */
  streamMode?: StreamMode | StreamMode[];
  /** Enable subgraph streaming */
  streamSubgraphs?: boolean;
  /** Initial input data */
  input?: Record<string, any>;
  /** Run configuration */
  config?: Config;
  /** Run metadata */
  metadata?: Metadata;
  /** Custom event handling */
  onEvent?: (event: StreamEvent) => void;
  /** Error handler */
  onError?: (error: Error) => void;
  /** Additional bag data for context */
  bag?: Bag;
}

interface UseStream<StateType, Bag> {
  /** Current state values from the stream */
  values: StateType | null;

  /** Current error state */
  error: Error | null;

  /** Loading state indicator */
  isLoading: boolean;

  /** Function to stop the current stream */
  stop: () => void;

  /** Function to submit input and start streaming */
  submit: (input: Record<string, any>, options?: SubmitOptions) => Promise<void>;

  /** Current execution branch information */
  branch: Branch | null;

  /** Function to set/switch execution branch */
  setBranch: (branch: Branch) => void;

  /** Complete execution history */
  history: HistoryItem<StateType>[];

  /** Experimental branch tree structure */
  experimental_branchTree: BranchTree | null;

  /** Function to interrupt current execution */
  interrupt: (options?: InterruptOptions) => Promise<void>;

  /** Message history from the stream */
  messages: Message[];

  /** Function to get metadata for messages */
  getMessagesMetadata: (messageIds: string[]) => MessageMetadata<StateType>[];

  /** LangGraph client instance */
  client: Client;

  /** Current assistant ID */
  assistantId: string;

  /** Function to join an existing stream */
  joinStream: (runId: string, options?: JoinStreamOptions) => Promise<void>;

  /** Additional context bag data */
  bag: Bag;
}

LoadExternalComponent

React component for loading external UI components dynamically with stream context.

function LoadExternalComponent(props: LoadExternalComponentProps): React.ReactElement;

interface LoadExternalComponentProps {
  /** Stream object from useStream hook */
  stream: UseStream<any, any>;
  /** UI message to render */
  message: UIMessage<any, any>;
  /** Optional namespace for component resolution */
  namespace?: string;
  /** Optional metadata */
  meta?: any;
  /** Fallback component if loading fails */
  fallback?: React.ComponentType<any>;
  /** Component registry for custom components */
  components?: Record<string, React.ComponentType<any>>;
  /** Inline styles */
  style?: React.CSSProperties;
  /** CSS class name */
  className?: string;
}

function useStreamContext<StateType, Bag>(): UseStreamContext<StateType, Bag>;

interface UseStreamContext<StateType, Bag> {
  /** Current stream instance */
  stream: UseStream<StateType, Bag>;
  /** Current message being rendered */
  message: UIMessage<any, any>;
  /** Namespace context */
  namespace?: string;
  /** Additional metadata */
  meta?: any;
}

UI Message System

Type-safe UI message management with reducers and utilities.

interface UIMessage<TName extends string = string, TProps = any> {
  /** Message type identifier */
  type: TName;
  /** Message properties */
  props: TProps;
  /** Unique message ID */
  id: string;
  /** Message timestamp */
  timestamp: string;
  /** Optional message metadata */
  metadata?: Record<string, any>;
}

interface RemoveUIMessage {
  /** Action type for removal */
  type: "remove";
  /** ID of message to remove */
  id: string;
}

// Type guard functions
function isUIMessage(message: unknown): message is UIMessage;
function isRemoveUIMessage(message: unknown): message is RemoveUIMessage;

// UI message reducer for state management
function uiMessageReducer(
  state: UIMessage[],
  action: UIMessage | RemoveUIMessage
): UIMessage[];

/**
 * Experimental function for loading shared UI components
 * @param options - Configuration options for shared component loading
 * @returns Promise resolving to loaded shared component
 */
function experimental_loadShare(options: LoadShareOptions): Promise<any>;

interface LoadShareOptions {
  /** Component identifier to load */
  componentId: string;
  /** Optional namespace for component resolution */
  namespace?: string;
  /** Component loading options */
  options?: Record<string, unknown>;
}

Server Utilities

Server-side helpers for type-safe UI component rendering.

function typedUi<Decl extends Record<string, React.ElementType>>(
  config: TypedUiConfig<Decl>,
  options?: TypedUiOptions
): TypedUiInstance<Decl>;

interface TypedUiConfig<Decl extends Record<string, React.ElementType>> {
  /** Component declarations mapping names to types */
  components: Decl;
  /** Default namespace for components */
  namespace?: string;
}

interface TypedUiOptions {
  /** Enable development mode features */
  dev?: boolean;
  /** Custom component resolver */
  resolver?: (name: string) => React.ElementType | undefined;
}

interface TypedUiInstance<Decl extends Record<string, React.ElementType>> {
  /** Push a new UI message */
  push<K extends keyof Decl>(
    name: K,
    props: React.ComponentProps<Decl[K]>
  ): UIMessage<K, React.ComponentProps<Decl[K]>>;

  /** Delete a UI message by ID */
  delete(id: string): RemoveUIMessage;

  /** Current UI message items */
  items: UIMessage[];
}

Supporting Types

Stream and Branch Management

interface Branch {
  /** Branch identifier */
  id: string;
  /** Branch name */
  name?: string;
  /** Parent branch ID */
  parentId?: string;
  /** Branch checkpoint */
  checkpoint: string;
  /** Branch metadata */
  metadata?: Record<string, any>;
}

interface BranchTree {
  /** Root branch */
  root: Branch;
  /** All branches in tree */
  branches: Record<string, Branch>;
  /** Current active branch */
  current: string;
}

interface HistoryItem<StateType> {
  /** History item ID */
  id: string;
  /** Associated checkpoint */
  checkpoint: string;
  /** State values at this point */
  values: StateType;
  /** Timestamp */
  timestamp: string;
  /** Associated messages */
  messages: Message[];
  /** Metadata */
  metadata?: Record<string, any>;
}

interface MessageMetadata<StateType> {
  /** Message ID */
  messageId: string;
  /** Associated branch */
  branch: Branch;
  /** State at message time */
  state: StateType;
  /** Message timestamp */
  timestamp: string;
}

Submit and Interrupt Options

interface SubmitOptions {
  /** Override stream mode */
  streamMode?: StreamMode | StreamMode[];
  /** Override configuration */
  config?: Config;
  /** Additional metadata */
  metadata?: Metadata;
  /** Reset stream state before submit */
  reset?: boolean;
}

interface InterruptOptions {
  /** Interrupt after specific nodes */
  after?: string[];
  /** Interrupt before specific nodes */
  before?: string[];
  /** Interrupt action */
  action?: "interrupt" | "rollback";
}

interface JoinStreamOptions {
  /** Stream modes to join */
  streamMode?: StreamMode | StreamMode[];
  /** Enable subgraph streaming */
  streamSubgraphs?: boolean;
}

Usage Examples

Basic Streaming Hook

import React, { useState } from 'react';
import { useStream } from '@langchain/langgraph-sdk/react';
import { Client } from '@langchain/langgraph-sdk';

const client = new Client({
  apiUrl: 'https://api.langgraph.example.com',
  apiKey: 'your-api-key'
});

interface AppState {
  messages: Array<{ role: string; content: string }>;
  context: Record<string, any>;
}

export function ChatInterface() {
  const [input, setInput] = useState('');

  const stream = useStream<AppState>({
    client,
    assistantId: 'assistant_123',
    streamMode: ['values', 'messages'],
    onError: (error) => {
      console.error('Stream error:', error);
    }
  });

  const handleSubmit = async () => {
    if (!input.trim()) return;

    await stream.submit({
      message: input,
      timestamp: new Date().toISOString()
    });

    setInput('');
  };

  return (
    <div className="chat-interface">
      <div className="messages">
        {stream.messages.map((message, index) => (
          <div key={index} className={`message ${message.type}`}>
            <div className="content">{message.content}</div>
            <div className="timestamp">
              {new Date(message.timestamp).toLocaleTimeString()}
            </div>
          </div>
        ))}
      </div>

      {stream.isLoading && (
        <div className="loading">Processing...</div>
      )}

      {stream.error && (
        <div className="error">
          Error: {stream.error.message}
          <button onClick={() => window.location.reload()}>
            Retry
          </button>
        </div>
      )}

      <div className="input-area">
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && handleSubmit()}
          placeholder="Type your message..."
          disabled={stream.isLoading}
        />
        <button
          onClick={handleSubmit}
          disabled={stream.isLoading || !input.trim()}
        >
          Send
        </button>
        {stream.isLoading && (
          <button onClick={stream.stop}>Stop</button>
        )}
      </div>

      {stream.values && (
        <div className="debug-info">
          <h3>Current State:</h3>
          <pre>{JSON.stringify(stream.values, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

Advanced Stream Management with Branches

import React, { useState, useEffect } from 'react';
import { useStream } from '@langchain/langgraph-sdk/react';

export function AdvancedStreamManager() {
  const [selectedBranch, setSelectedBranch] = useState<string | null>(null);

  const stream = useStream({
    client,
    assistantId: 'assistant_advanced',
    streamMode: ['values', 'updates', 'messages', 'debug'],
    streamSubgraphs: true,
    onEvent: (event) => {
      console.log('Stream event:', event);
    }
  });

  // Handle branch switching
  useEffect(() => {
    if (selectedBranch && stream.setBranch) {
      const branch = stream.experimental_branchTree?.branches[selectedBranch];
      if (branch) {
        stream.setBranch(branch);
      }
    }
  }, [selectedBranch, stream]);

  const handleInterrupt = async () => {
    await stream.interrupt({
      after: ['node_1', 'node_2'],
      action: 'interrupt'
    });
  };

  const renderBranchTree = () => {
    if (!stream.experimental_branchTree) return null;

    const { branches, current } = stream.experimental_branchTree;

    return (
      <div className="branch-tree">
        <h3>Execution Branches</h3>
        {Object.entries(branches).map(([id, branch]) => (
          <div
            key={id}
            className={`branch ${id === current ? 'active' : ''}`}
            onClick={() => setSelectedBranch(id)}
          >
            <div className="branch-name">
              {branch.name || `Branch ${id.slice(0, 8)}`}
            </div>
            <div className="branch-checkpoint">
              Checkpoint: {branch.checkpoint.slice(0, 8)}...
            </div>
          </div>
        ))}
      </div>
    );
  };

  const renderHistory = () => (
    <div className="history">
      <h3>Execution History</h3>
      {stream.history.map((item, index) => (
        <div key={item.id} className="history-item">
          <div className="history-header">
            <span className="checkpoint">
              {item.checkpoint.slice(0, 8)}...
            </span>
            <span className="timestamp">
              {new Date(item.timestamp).toLocaleString()}
            </span>
          </div>
          <div className="history-content">
            <pre>{JSON.stringify(item.values, null, 2)}</pre>
          </div>
        </div>
      ))}
    </div>
  );

  return (
    <div className="advanced-stream-manager">
      <div className="controls">
        <button
          onClick={() => stream.submit({ action: 'analyze' })}
          disabled={stream.isLoading}
        >
          Start Analysis
        </button>
        <button
          onClick={handleInterrupt}
          disabled={!stream.isLoading}
        >
          Interrupt
        </button>
        <button onClick={stream.stop}>Stop</button>
      </div>

      <div className="content">
        <div className="left-panel">
          {renderBranchTree()}
        </div>

        <div className="main-panel">
          <div className="current-state">
            <h3>Current State</h3>
            {stream.values ? (
              <pre>{JSON.stringify(stream.values, null, 2)}</pre>
            ) : (
              <p>No state data</p>
            )}
          </div>

          {stream.messages.length > 0 && (
            <div className="messages">
              <h3>Messages</h3>
              {stream.messages.map((message, index) => (
                <div key={index} className="message">
                  <strong>{message.type}:</strong> {message.content}
                </div>
              ))}
            </div>
          )}
        </div>

        <div className="right-panel">
          {renderHistory()}
        </div>
      </div>
    </div>
  );
}

Dynamic UI Components

import React from 'react';
import { LoadExternalComponent, useStream } from '@langchain/langgraph-sdk/react';

// Custom UI components
const ChartComponent = ({ data, title }: { data: number[]; title: string }) => (
  <div className="chart">
    <h3>{title}</h3>
    <div className="bars">
      {data.map((value, index) => (
        <div
          key={index}
          className="bar"
          style={{ height: `${value}px` }}
        />
      ))}
    </div>
  </div>
);

const FormComponent = ({ fields, onSubmit }: {
  fields: Array<{ name: string; type: string; label: string }>;
  onSubmit: (data: Record<string, any>) => void;
}) => {
  const [formData, setFormData] = useState({});

  return (
    <form onSubmit={(e) => { e.preventDefault(); onSubmit(formData); }}>
      {fields.map(field => (
        <div key={field.name} className="field">
          <label>{field.label}</label>
          <input
            type={field.type}
            onChange={(e) => setFormData({
              ...formData,
              [field.name]: e.target.value
            })}
          />
        </div>
      ))}
      <button type="submit">Submit</button>
    </form>
  );
};

export function DynamicUIDemo() {
  const stream = useStream({
    client,
    assistantId: 'ui_assistant',
    streamMode: ['values', 'custom'],
    onEvent: (event) => {
      // Handle custom UI events
      if (event.event === 'custom' && isUIMessage(event.data)) {
        console.log('UI message received:', event.data);
      }
    }
  });

  // Component registry
  const components = {
    chart: ChartComponent,
    form: FormComponent
  };

  const renderUIMessages = () => {
    // Extract UI messages from stream
    const uiMessages = stream.messages.filter(isUIMessage);

    return uiMessages.map(message => (
      <LoadExternalComponent
        key={message.id}
        stream={stream}
        message={message}
        components={components}
        fallback={() => <div>Component not found: {message.type}</div>}
        className="dynamic-component"
      />
    ));
  };

  return (
    <div className="dynamic-ui-demo">
      <div className="controls">
        <button onClick={() => stream.submit({
          action: 'generate_chart',
          data: [10, 20, 30, 15, 25]
        })}>
          Generate Chart
        </button>
        <button onClick={() => stream.submit({
          action: 'create_form',
          fields: [
            { name: 'name', type: 'text', label: 'Name' },
            { name: 'email', type: 'email', label: 'Email' }
          ]
        })}>
          Create Form
        </button>
      </div>

      <div className="ui-components">
        {renderUIMessages()}
      </div>

      {stream.error && (
        <div className="error">Error: {stream.error.message}</div>
      )}
    </div>
  );
}

UI Message Management with Reducer

import React, { useReducer, useEffect } from 'react';
import { uiMessageReducer, UIMessage, RemoveUIMessage } from '@langchain/langgraph-sdk/react-ui';

export function UIMessageManager() {
  const [uiMessages, dispatchUIMessage] = useReducer(uiMessageReducer, []);

  const stream = useStream({
    client,
    assistantId: 'message_assistant',
    onEvent: (event) => {
      if (event.event === 'custom') {
        const data = event.data;

        if (isUIMessage(data)) {
          dispatchUIMessage(data);
        } else if (isRemoveUIMessage(data)) {
          dispatchUIMessage(data);
        }
      }
    }
  });

  // Simulate adding messages
  const addMessage = (type: string, props: any) => {
    const message: UIMessage = {
      type,
      props,
      id: `msg_${Date.now()}`,
      timestamp: new Date().toISOString()
    };
    dispatchUIMessage(message);
  };

  const removeMessage = (id: string) => {
    const removeAction: RemoveUIMessage = {
      type: 'remove',
      id
    };
    dispatchUIMessage(removeAction);
  };

  return (
    <div className="ui-message-manager">
      <div className="controls">
        <button onClick={() => addMessage('notification', {
          title: 'Success',
          message: 'Operation completed',
          type: 'success'
        })}>
          Add Success Notification
        </button>

        <button onClick={() => addMessage('progress', {
          value: 50,
          label: 'Processing...'
        })}>
          Add Progress Bar
        </button>

        <button onClick={() => {
          if (uiMessages.length > 0) {
            removeMessage(uiMessages[0].id);
          }
        }}>
          Remove First Message
        </button>
      </div>

      <div className="messages">
        {uiMessages.map(message => (
          <div key={message.id} className="ui-message">
            <div className="message-header">
              <span className="type">{message.type}</span>
              <button onClick={() => removeMessage(message.id)}>×</button>
            </div>
            <div className="message-content">
              <pre>{JSON.stringify(message.props, null, 2)}</pre>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

Server-Side UI Utilities

// Server-side component definition
import { typedUi } from '@langchain/langgraph-sdk/react-ui/server';

// Define component types
interface ButtonProps {
  label: string;
  onClick?: () => void;
  variant?: 'primary' | 'secondary';
}

interface CardProps {
  title: string;
  content: string;
  actions?: ButtonProps[];
}

// Create typed UI instance
const ui = typedUi({
  components: {
    button: {} as React.ComponentType<ButtonProps>,
    card: {} as React.ComponentType<CardProps>
  },
  namespace: 'app'
});

// Usage in LangGraph node
function createUIMessage() {
  // Type-safe UI message creation
  const buttonMessage = ui.push('button', {
    label: 'Click me',
    variant: 'primary',
    onClick: () => console.log('Button clicked')
  });

  const cardMessage = ui.push('card', {
    title: 'Information Card',
    content: 'This is some important information',
    actions: [
      { label: 'Accept', variant: 'primary' },
      { label: 'Decline', variant: 'secondary' }
    ]
  });

  return {
    ui_messages: [buttonMessage, cardMessage]
  };
}

// Delete UI messages
function cleanupUI() {
  const deleteMessage = ui.delete('message_id_123');
  return { ui_messages: [deleteMessage] };
}

Error Handling and Recovery

export function RobustStreamingApp() {
  const [retryCount, setRetryCount] = useState(0);
  const maxRetries = 3;

  const stream = useStream({
    client,
    assistantId: 'reliable_assistant',
    onError: (error) => {
      console.error('Stream error:', error);

      // Automatic retry with backoff
      if (retryCount < maxRetries) {
        const delay = Math.pow(2, retryCount) * 1000; // Exponential backoff
        setTimeout(() => {
          setRetryCount(prev => prev + 1);
          stream.submit({ retry: true, attempt: retryCount + 1 });
        }, delay);
      }
    }
  });

  const handleManualRetry = () => {
    setRetryCount(0);
    stream.submit({ manual_retry: true });
  };

  return (
    <div className="robust-streaming-app">
      {stream.error && (
        <div className="error-banner">
          <div className="error-message">
            {stream.error.message}
          </div>
          <div className="error-actions">
            {retryCount < maxRetries ? (
              <div>Retrying... (Attempt {retryCount + 1}/{maxRetries})</div>
            ) : (
              <button onClick={handleManualRetry}>
                Retry Manually
              </button>
            )}
          </div>
        </div>
      )}

      <div className="stream-content">
        {stream.values && (
          <pre>{JSON.stringify(stream.values, null, 2)}</pre>
        )}
      </div>

      <div className="stream-status">
        <div>Loading: {stream.isLoading ? 'Yes' : 'No'}</div>
        <div>Error: {stream.error ? 'Yes' : 'No'}</div>
        <div>Retry Count: {retryCount}</div>
      </div>
    </div>
  );
}

The React integration provides comprehensive tools for building interactive LangGraph applications with real-time streaming, dynamic UI components, and robust state management, making it easy to create responsive and feature-rich user interfaces.

docs

assistants.md

auth.md

client.md

crons.md

index.md

react.md

runs.md

store.md

threads.md

tile.json