Real-Time Streaming

Updated

The Chaos AI SDK streams responses incrementally. Instead of waiting for the full response, you can process each message as it arrives using the onStreamEvent callback. This lets you build responsive UIs that show progress in real time.

How streaming works

When you call chaos.chat.responses.create(), the SDK opens an HTTP connection and reads NDJSON lines as they stream in. Each line is parsed into a ChaosSDKMessage and delivered to your onStreamEvent callback. After the stream completes, the method returns a ChatCreateResponse containing all collected messages.

The onStreamEvent callback

Add onStreamEvent to your ChatCreateRequestParams to receive each message as it arrives:

streaming-basic.ts
import { Chaos, WALLET_MODEL } from '@chaoslabs/ai-sdk';
 
const chaos = new Chaos({
  apiKey: process.env.CHAOS_API_KEY!,
});
 
const response = await chaos.chat.responses.create({
  model: WALLET_MODEL,
  input: [
    { type: 'message', role: 'user', content: 'Analyze my DeFi positions' },
  ],
  metadata: {
    user_id: 'user-123',
    session_id: 'session-abc',
    wallets: [{ address: '0xYourWallet', chain: 'ethereum' }],
  },
  onStreamEvent: (message) => {
    console.log(`[${message.type}]`, message.id);
  },
});
 
// response.messages contains all the messages that were streamed
console.log('Total messages:', response.messages?.length);
[@portabletext/react] Unknown block type "callout", specify a component for it in the `components.types` prop

ChaosSDKMessage types

Every streamed message has a kind of "message" and a type that tells you what it contains. The five message types are:

typeInterfaceDescription
"status"AgentStatusSDKMessageAgent status change ("processing" or "done")
"text"AgentTextSDKMessageIncremental text content
"block"BlockSDKMessageA structured block (table, chart, action, etc.)
"follow_up_suggestions"FollowUpSuggestionsSDKMessageSuggested follow-up queries
"input"UserInputSDKMessageA disambiguation prompt with options

Handling each message type

Here is a complete handler that processes every message type:

streaming-handler.ts
import { Chaos, WALLET_MODEL } from '@chaoslabs/ai-sdk';
import type { ChaosSDKMessage } from '@chaoslabs/ai-sdk';
 
const chaos = new Chaos({
  apiKey: process.env.CHAOS_API_KEY!,
});
 
function handleStreamEvent(message: ChaosSDKMessage): void {
  switch (message.type) {
    case 'status':
      // Agent status: 'processing' or 'done'
      console.log(`Status: ${message.data.status}`);
      break;
 
    case 'text':
      // Incremental text content
      process.stdout.write(message.data.text);
      break;
 
    case 'block':
      // Structured block (table, chart, action, markdown, etc.)
      const block = message.data.block;
      console.log(`\n[Block: ${block.type}]`);
      if ('title' in block && block.title) {
        console.log(`  Title: ${block.title}`);
      }
      break;
 
    case 'follow_up_suggestions':
      // Suggested next questions
      console.log('\nSuggested follow-ups:');
      for (const suggestion of message.data.suggestions) {
        console.log(`  - ${suggestion}`);
      }
      break;
 
    case 'input':
      // Disambiguation prompt
      console.log(`\nInput needed: ${message.data.prompt}`);
      for (const option of message.data.options) {
        console.log(`  - ${option.name}`);
      }
      break;
  }
}
 
const response = await chaos.chat.responses.create({
  model: WALLET_MODEL,
  input: [
    { type: 'message', role: 'user', content: 'What are my top positions?' },
  ],
  metadata: {
    user_id: 'user-123',
    session_id: 'session-abc',
    wallets: [{ address: '0xYourWallet', chain: 'ethereum' }],
  },
  onStreamEvent: handleStreamEvent,
});

Building a progress display

A common pattern is to show a status indicator while the agent is processing, then render the content as it streams in:

streaming-progress.ts
import { Chaos, WALLET_MODEL, extractText, extractBlocks } from '@chaoslabs/ai-sdk';
import type { ChaosSDKMessage } from '@chaoslabs/ai-sdk';
 
const chaos = new Chaos({
  apiKey: process.env.CHAOS_API_KEY!,
});
 
let isProcessing = false;
let textChunks: string[] = [];
let blockCount = 0;
 
function onStreamEvent(message: ChaosSDKMessage): void {
  if (message.type === 'status') {
    if (message.data.status === 'processing') {
      isProcessing = true;
      console.log('Thinking...');
    } else if (message.data.status === 'done') {
      isProcessing = false;
      console.log('\nDone.');
    }
  }
 
  if (message.type === 'text') {
    textChunks.push(message.data.text);
    // Print text as it arrives
    process.stdout.write(message.data.text);
  }
 
  if (message.type === 'block') {
    blockCount++;
    const block = message.data.block;
    console.log(`\nReceived ${block.type} block`);
  }
}
 
const response = await chaos.chat.responses.create({
  model: WALLET_MODEL,
  input: [
    { type: 'message', role: 'user', content: 'Show me my Aave positions' },
  ],
  metadata: {
    user_id: 'user-123',
    session_id: 'session-abc',
    wallets: [{ address: '0xYourWallet', chain: 'ethereum' }],
  },
  onStreamEvent,
});
 
console.log(`\nStreamed ${textChunks.length} text chunks, ${blockCount} blocks`);
console.log(`Final response status: ${response.status}`);

Streaming event order

Events typically arrive in this order:

  1. status with data.status === "processing" -- the agent has started.
  2. text messages -- incremental text content.
  3. block messages -- structured data blocks (tables, charts, transactions).
  4. follow_up_suggestions -- suggested next queries.
  5. status with data.status === "done" -- the agent has finished.

Text and block messages can interleave. Blocks may arrive before all text is complete.

Cancelling a request

You can cancel an in-progress streaming request by calling cancel() on the responses object:

streaming-cancel.ts
// Start a request
const responsePromise = chaos.chat.responses.create({
  model: WALLET_MODEL,
  input: [
    { type: 'message', role: 'user', content: 'Detailed analysis of all my positions' },
  ],
  metadata: {
    user_id: 'user-123',
    session_id: 'session-abc',
    wallets: [{ address: '0xYourWallet', chain: 'ethereum' }],
  },
  onStreamEvent: (message) => {
    console.log(`[${message.type}]`);
  },
});
 
// Cancel after 5 seconds
setTimeout(() => {
  chaos.chat.responses.cancel();
}, 5000);
 
try {
  const response = await responsePromise;
  console.log('Completed:', response.status);
} catch (err) {
  console.log('Request was cancelled');
}

Error handling

If the connection fails or times out, the create() promise rejects with a ChaosError or ChaosTimeoutError:

streaming-errors.ts
import { Chaos, WALLET_MODEL, ChaosError, ChaosTimeoutError } from '@chaoslabs/ai-sdk';
 
const chaos = new Chaos({
  apiKey: process.env.CHAOS_API_KEY!,
  timeout: 60000, // 60-second timeout
});
 
try {
  const response = await chaos.chat.responses.create({
    model: WALLET_MODEL,
    input: [
      { type: 'message', role: 'user', content: 'What is my portfolio?' },
    ],
    metadata: {
      user_id: 'user-123',
      session_id: 'session-abc',
      wallets: [{ address: '0xYourWallet', chain: 'ethereum' }],
    },
    onStreamEvent: (message) => {
      if (message.type === 'text') {
        process.stdout.write(message.data.text);
      }
    },
  });
} catch (error) {
  if (error instanceof ChaosTimeoutError) {
    console.error('Request timed out');
  } else if (error instanceof ChaosError) {
    console.error('API error:', error.message, 'status:', error.status);
  } else {
    console.error('Unexpected error:', error);
  }
}

Next steps

  • Wallet Configuration -- Set up multi-chain wallets and context overrides for deterministic testing.
  • Quick Start -- Review the basics if you skipped ahead.
Was this helpful?