Documentation
SDK

SDK: Examples

End-to-end examples for building with Flapjack: basic chatbot, support agent with tools, and RAG-powered app.

Complete, working examples for common Flapjack use cases.

Example 1: Basic Chatbot (Node.js)

A CLI chatbot that streams responses to the terminal.

import { FlapjackClient } from '@flapjack/sdk';
import * as readline from 'readline';

const client = new FlapjackClient({
  apiKey: process.env.FLAPJACK_API_KEY!,
});

async function main() {
  // Get the first agent
  const agents = await client.listAgents();
  if (agents.length === 0) {
    console.error('No agents found. Create one at flapjack.chat');
    return;
  }

  const agent = agents[0];
  console.log(`Chatting with: ${agent.name}\n`);

  // Create a thread
  const thread = await client.createThread(agent.id);

  // Chat loop
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  const ask = (prompt: string) => new Promise<string>(resolve => rl.question(prompt, resolve));

  while (true) {
    const input = await ask('You: ');
    if (input === 'exit') break;

    process.stdout.write('Agent: ');
    for await (const event of client.sendMessage(thread.id, input)) {
      if (event.type === 'token') process.stdout.write(event.delta);
      if (event.type === 'tool_call') process.stdout.write(`\n[Using ${event.tool.name}...] `);
      if (event.type === 'error') console.error(`\nError: ${event.code}`);
    }
    console.log('\n');
  }

  rl.close();
}

main();
πŸ“‹ Copy as prompt

Build a CLI chatbot using Flapjack's SDK. It should list agents, create a thread with the first agent, then loop: read user input, send it to the agent, and stream the response token-by-token. Exit when the user types "exit".

Example 2: Support Agent with Tool Visibility

A React chat UI that shows when the agent is using tools.

import { useState } from 'react';
import { FlapjackProvider, useChat } from '@flapjack/sdk/react';

function SupportChat({ agentId }: { agentId: string }) {
  const { messages, sendMessage, isStreaming, error } = useChat(agentId);
  const [input, setInput] = useState('');
  const [activeTool, setActiveTool] = useState<string | null>(null);

  // Note: For tool visibility with useChat, you'd typically
  // use the lower-level client.sendMessage and manage state manually.
  // This example shows the simpler useChat approach.

  return (
    <div className="chat-container">
      <h2>Support</h2>

      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i} className={`message ${msg.role}`}>
            <span className="role">{msg.role === 'user' ? 'You' : 'Support'}</span>
            <p>{msg.content}</p>
          </div>
        ))}
        {isStreaming && <div className="typing">Agent is responding...</div>}
        {error && <div className="error">{error}</div>}
      </div>

      <form onSubmit={(e) => {
        e.preventDefault();
        if (input.trim() && !isStreaming) {
          sendMessage(input);
          setInput('');
        }
      }}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Describe your issue..."
          disabled={isStreaming}
        />
        <button type="submit" disabled={isStreaming}>Send</button>
      </form>
    </div>
  );
}

export default function App() {
  // ⚠️ NEXT_PUBLIC_ exposes the key client-side β€” use server proxy in production
  // See: docs/developer/sdk/server-proxy.md
  return (
    <FlapjackProvider config={{ apiKey: process.env.NEXT_PUBLIC_FLAPJACK_API_KEY! }}>
      <SupportChat agentId="your-support-agent-id" />
    </FlapjackProvider>
  );
}
πŸ“‹ Copy as prompt

Build a React support chat component using Flapjack's useChat hook. Include a message list with role labels, streaming indicator, error display, and a form with input and submit button.

Example 3: RAG-Powered App

Upload documents and ask questions against them.

import { FlapjackClient } from '@flapjack/sdk';
import { readFileSync } from 'fs';

const client = new FlapjackClient({
  apiKey: process.env.FLAPJACK_API_KEY!,
});

async function main() {
  const agentId = process.env.FLAPJACK_AGENT_ID!;

  // Upload a document
  const fileContent = readFileSync('./product-docs.md');
  const blob = new Blob([fileContent], { type: 'text/markdown' });
  const doc = await client.uploadDocument(agentId, blob, 'Product Documentation');
  console.log(`Uploaded: ${doc.title}\n`);

  // List all documents
  const docs = await client.listDocuments(agentId);
  console.log('Knowledge base:');
  docs.forEach(d => console.log(`  - ${d.title} (${d.chunk_count} chunks)`));
  console.log();

  // Create a thread and ask a question
  const thread = await client.createThread(agentId);

  console.log('Asking: "What are the pricing tiers?"\n');
  process.stdout.write('Agent: ');
  for await (const event of client.sendMessage(thread.id, 'What are the pricing tiers?')) {
    if (event.type === 'token') process.stdout.write(event.delta);
    if (event.type === 'done') console.log('\n');
  }

  // Clean up (optional)
  // await client.deleteDocument(doc.id);
}

main();
πŸ“‹ Copy as prompt

Build a Flapjack RAG script that uploads a markdown file to an agent's knowledge base, lists all documents, creates a thread, asks a question about the uploaded content, and streams the answer.

Example 4: System Activity Messages

Show system activity as compact status indicators instead of user bubbles β€” useful for plan approvals, tool confirmations, and custom events.

import { FlapjackProvider, useChat } from '@flapjack/sdk/react';
import { ChatMessages, ChatInput, SystemUserMessage } from '@flapjack/sdk/components';
import '@flapjack/sdk/components/style.css';

function AgentChat({ agentId }: { agentId: string }) {
  const { messages, sendMessage, addSystemMessage, isStreaming } = useChat(agentId);

  return (
    <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
      <ChatMessages messages={messages} isStreaming={isStreaming} />

      <div style={{ padding: 12, display: 'flex', gap: 8 }}>
        {/* Example: manual system activity buttons */}
        <button onClick={() => addSystemMessage(
          { icon: 'check', label: 'Plan approved' },
          { content: 'Approved. Please proceed with the plan.' },
        )}>
          Approve Plan
        </button>

        <button onClick={() => addSystemMessage(
          { icon: 'πŸš€', label: 'Deployment started' },
          { sendToAgent: false },
        )}>
          Deploy (visual only)
        </button>
      </div>

      <ChatInput onSend={sendMessage} isStreaming={isStreaming} />
    </div>
  );
}

export default function App() {
  return (
    <FlapjackProvider config={{ apiKey: process.env.NEXT_PUBLIC_FLAPJACK_API_KEY! }}>
      <AgentChat agentId="your-agent-id" />
    </FlapjackProvider>
  );
}

Key points:

  • addSystemMessage renders a centered icon + label pill instead of a user bubble
  • Set sendToAgent: false for visual-only indicators (no agent turn triggered)
  • Built-in icons: check (βœ“), tool (πŸ”§), info (β„Ή), error (βœ•) β€” or pass any emoji
Copy as prompt

Build a React chat with Flapjack that uses addSystemMessage from useChat to show system activity indicators. Add buttons for plan approval (sends to agent) and deployment notification (visual only). Use the SDK's ChatMessages and ChatInput components.

Example 5: Custom Events

Handle domain-specific events emitted by tools during streaming β€” feature suggestions, navigation proposals, auth challenges, etc.

import { FlapjackProvider, useChat } from '@flapjack/sdk/react';
import { ChatMessages, ChatInput } from '@flapjack/sdk/components';
import { useState } from 'react';

type Suggestion = { name: string; description: string; icon: string };
type NavigationProposal = { url: string; label: string };

function AgentChat({ agentId }: { agentId: string }) {
  const [suggestions, setSuggestions] = useState<Suggestion[]>([]);

  const { messages, sendMessage, isStreaming } = useChat(agentId, {
    onCustomEvent(kind, payload) {
      if (kind === 'suggestion') {
        setSuggestions((prev) => [...prev, payload as Suggestion]);
      }
      if (kind === 'navigation_proposal') {
        const { url, label } = payload as NavigationProposal;
        window.location.href = url; // or show a confirmation card
      }
    },
  });

  return (
    <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
      <ChatMessages messages={messages} isStreaming={isStreaming} />

      {suggestions.length > 0 && (
        <div style={{ padding: 8, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          {suggestions.map((s, i) => (
            <button key={i} style={{ padding: '4px 12px', borderRadius: 16, border: '1px solid #ccc' }}>
              {s.icon} {s.name}
            </button>
          ))}
        </div>
      )}

      <ChatInput onSend={sendMessage} isStreaming={isStreaming} />
    </div>
  );
}

export default function App() {
  return (
    <FlapjackProvider config={{ apiKey: process.env.NEXT_PUBLIC_FLAPJACK_API_KEY! }}>
      <AgentChat agentId="your-agent-id" />
    </FlapjackProvider>
  );
}

How it works:

  • Tools return _client_events in their output β€” the runtime strips them before feeding back to the LLM and emits each as a custom SSE event
  • onCustomEvent fires for each custom event with kind (string identifier) and payload (arbitrary JSON)
  • Use kind to dispatch to the right handler β€” suggestions, navigation, auth challenges, etc.
Copy as prompt

Build a React chat with Flapjack that handles custom SSE events using onCustomEvent in useChat. Handle "suggestion" events by collecting them into a button bar and "navigation_proposal" events by navigating the browser.

Next Steps

Docs last updated May 11, 2026