Chat SDK

Overview

Stream forwarding lets agents control any component in your application. When an agent generates content, you can forward it to code editors, preview panels, file trees, or any custom component - making agents truly interactive with your UI.

The Power of Forwarding

Instead of just showing agent responses in chat, you can:

  • Update code editors as agents write code
  • Populate forms with generated data
  • Render previews of created content
  • Update databases with structured data
  • Control UI state based on agent actions

Basic Forwarding

Single Action

Forward agent output to your component:

import { useForwarding } from 'react-chat-agent';

function CodeEditor() {
  const [code, setCode] = useState('');

  useForwarding('code_generation', (incrementalText, fullText) => {
    setCode(fullText); // Update editor with agent's code
  }, {
    mode: 'replace', // Don't show in chat, only in editor
    animated: false  // Instant updates
  });

  return <MonacoEditor value={code} />;
}

Multiple Actions

Control different parts of your app:

function DevEnvironment() {
  const [code, setCode] = useState('');
  const [readme, setReadme] = useState('');
  const [tests, setTests] = useState('');

  useForwarding({
    write_code: {
      handler: (inc, full) => setCode(full),
      mode: 'replace'
    },
    write_readme: {
      handler: (inc, full) => setReadme(full),
      mode: 'replace'
    },
    write_tests: {
      handler: (inc, full) => setTests(full),
      mode: 'replace'
    }
  });

  return (
    <div className="grid grid-cols-3 gap-4">
      <Editor value={code} language="typescript" />
      <MarkdownPreview value={readme} />
      <Editor value={tests} language="typescript" />
    </div>
  );
}

Forwarding Modes

Replace Mode

Agent output goes ONLY to your component, not chat:

useForwarding('generate_report', handler, {
  mode: 'replace' // ← Hide from chat, show only where you want
});

Use case: Code editors, form fields, databases - anywhere you want full control.

Dual Mode

Agent output appears in BOTH chat and your component:

useForwarding('explain_code', handler, {
  mode: 'dual' // ← Show in chat AND your component
});

Use case: When you want users to see the conversation while also updating your UI.

Real-World Examples

AI Code Editor

Agent writes code directly into your Monaco editor:

function AICodeEditor() {
  const editorRef = useRef();
  const [language, setLanguage] = useState('javascript');

  useForwarding('write_code', (incremental, full, { event }) => {
    if (editorRef.current) {
      editorRef.current.setValue(full);
    }
  }, {
    mode: 'replace',
    animated: false
  });

  return (
    <div>
      <select onChange={(e) => setLanguage(e.value)}>
        <option>javascript</option>
        <option>python</option>
        <option>rust</option>
      </select>
      <MonacoEditor
        ref={editorRef}
        language={language}
        theme="vs-dark"
      />
    </div>
  );
}

Live Documentation Generator

Agent creates docs that render in real-time:

function LiveDocsGenerator() {
  const [markdown, setMarkdown] = useState('');

  useForwarding('generate_docs', (inc, full) => {
    setMarkdown(full);
  }, {
    mode: 'replace',
    animated: true // Typing effect
  });

  return (
    <div className="grid grid-cols-2 gap-4">
      <div className="prose">
        <ReactMarkdown>{markdown}</ReactMarkdown>
      </div>
      <div className="preview">
        <ComponentPreview code={markdown} />
      </div>
    </div>
  );
}

Form Auto-Fill

Agent fills forms with structured data:

function SmartForm() {
  const [formData, setFormData] = useState({});

  useForwarding('extract_data', (inc, full, { event }) => {
    if (event === 'end') {
      try {
        const data = JSON.parse(full);
        setFormData(data);
      } catch (e) {
        console.error('Invalid JSON');
      }
    }
  }, {
    mode: 'replace'
  });

  return (
    <form>
      <input value={formData.name || ''} />
      <input value={formData.email || ''} />
      <input value={formData.company || ''} />
    </form>
  );
}

Multi-File Editor

Agent creates entire project structures:

function ProjectEditor() {
  const [files, setFiles] = useState({});
  const [activeFile, setActiveFile] = useState('');

  useForwarding({
    create_component: {
      handler: (inc, full, { event }) => {
        if (event === 'end') {
          setFiles(prev => ({
            ...prev,
            'Component.tsx': full
          }));
        }
      },
      mode: 'replace'
    },
    create_styles: {
      handler: (inc, full, { event }) => {
        if (event === 'end') {
          setFiles(prev => ({
            ...prev,
            'styles.css': full
          }));
        }
      },
      mode: 'replace'
    },
    create_test: {
      handler: (inc, full, { event }) => {
        if (event === 'end') {
          setFiles(prev => ({
            ...prev,
            'Component.test.tsx': full
          }));
        }
      },
      mode: 'replace'
    }
  });

  return (
    <div className="flex h-screen">
      <FileTree files={files} onSelect={setActiveFile} />
      <Editor file={files[activeFile]} />
    </div>
  );
}

Handler Function

Parameters

handler(incrementalText, fullText, metadata)
  • incrementalText: New text since last chunk (for appending)
  • fullText: Complete text from start (for replacing)
  • metadata: { action, isLive, event: 'chunk' | 'end' }

Usage Patterns

Append mode (for live updates):

handler: (inc, full) => {
  setText(prev => prev + inc); // Append new text
}

Replace mode (for final value):

handler: (inc, full, { event }) => {
  if (event === 'end') {
    setText(full); // Set complete text once done
  }
}

Mixed mode (live updates + final processing):

handler: (inc, full, { event }) => {
  setPreview(full); // Live preview
  
  if (event === 'end') {
    saveToDatabase(full); // Final save
    generatePDF(full);
    notify('Generation complete');
  }
}

Provider-Level Forwarding

Set up global handlers in ChatProvider:

<ChatProvider
  authConfig={authConfig}
  forwardingHandlers={{
    code_generation: {
      handler: updateCodeEditor,
      mode: 'replace',
      animated: false
    },
    design_generation: {
      handler: updateDesignCanvas,
      mode: 'replace'
    }
  }}
>
  <App />
</ChatProvider>

Dynamic Registration

Register/unregister handlers on the fly:

function DynamicHandler() {
  const forwarding = useForwarding();
  const [enabled, setEnabled] = useState(false);

  useEffect(() => {
    if (enabled) {
      return forwarding.register('custom_action', handler, {
        mode: 'replace'
      });
    }
  }, [enabled]);

  return (
    <button onClick={() => setEnabled(!enabled)}>
      {enabled ? 'Disable' : 'Enable'} Handler
    </button>
  );
}

Animation Control

Instant Updates

For code editors and data:

useForwarding('write_code', handler, {
  animated: false // No typing animation
});

Typing Effect

For user-facing text:

useForwarding('explain', handler, {
  animated: true // Smooth typing effect
});

Advanced Patterns

Conditional Forwarding

Forward based on conditions:

function ConditionalForwarding() {
  const [mode, setMode] = useState('preview');

  useForwarding('generate_content', (inc, full) => {
    if (mode === 'preview') {
      updatePreview(full);
    } else if (mode === 'edit') {
      updateEditor(full);
    }
  });
}

Multi-Target Forwarding

Send same content to multiple components:

useForwarding('generate_article', (inc, full) => {
  updateEditor(full);
  updatePreview(full);
  updateWordCount(full.split(' ').length);
  updateReadTime(calculateReadTime(full));
});

Transformation Pipeline

Transform data before forwarding:

useForwarding('generate_json', (inc, full, { event }) => {
  if (event === 'end') {
    try {
      const parsed = JSON.parse(full);
      const transformed = transformData(parsed);
      setData(transformed);
    } catch (e) {
      setError('Invalid JSON');
    }
  }
});

Best Practices

Performance

// ✅ Use incremental for appending
handler: (inc) => setText(prev => prev + inc)

// ❌ Don't replace entire text each chunk
handler: (inc, full) => setText(full) // Causes re-renders

Memory Management

// ✅ Clean up on event end
handler: (inc, full, { event }) => {
  updatePreview(full);
  
  if (event === 'end') {
    clearTempData();
    optimizeContent(full);
  }
}

Error Handling

handler: (inc, full, { event }) => {
  try {
    const data = processContent(full);
    updateUI(data);
  } catch (error) {
    console.error('Forwarding error:', error);
    showError('Failed to process content');
  }
}

TypeScript Types

import type {
  ForwardingHandler,
  ForwardingHandlerConfig,
  ForwardingMetadata
} from 'react-chat-agent';

const handler: ForwardingHandler = (inc, full, meta) => {
  // Type-safe handler
};

const config: ForwardingHandlerConfig = {
  handler,
  mode: 'replace',
  animated: false
};

Next Steps