Skip to main content
AI operations take time. Streaming shows real-time progress as the agent works.

Event Types

type SSEEvent<T> = ProgressEvent | CompleteEvent<T> | ErrorEvent | PreviewEvent | ReasoningEvent;
Every streaming method returns the same SSEEvent union, so the patterns below work for all of them.

ProgressEvent

A human-readable progress update. message is always set; the agent-loop fields are present on events emitted from inside an agent.
interface ProgressEvent {
  type: 'progress';
  message: string;
  agentId?: string;
  executionId?: string;
  iteration?: number;
  metadata?: Record<string, unknown>;
}

CompleteEvent

interface CompleteEvent<T> {
  type: 'complete';
  data: T;
}
The final event on a successful stream. data is the response type of the method you called — for example, a generateGraphStream() stream completes with a GenerateGraphResponse.

ErrorEvent

interface ErrorEvent {
  type: 'error';
  error: string;
  code?: string;
  retryable?: boolean;
}

PreviewEvent

An incremental preview of the agent’s work-in-progress result. The payload is spread at the top level, and its shape depends on the agent — Mutation previews carry config.
interface PreviewEvent {
  type: 'preview';
  [key: string]: unknown;
}

ReasoningEvent

A reasoning message emitted by an agent as it works — useful for surfacing the agent’s thinking in a UI.
interface ReasoningEvent {
  type: 'reasoning';
  agentId: string;
  executionId: string;
  message: string;
}

Async Iterator Pattern

generateGraphStream() returns an async iterator:
import { isProgressEvent, isCompleteEvent, isErrorEvent } from '@graphysdk/agents-sdk';

const stream = await ai.generateGraphStream({
  config,
  userPrompt: 'Add a trend line',
});

for await (const event of stream) {
  if (isProgressEvent(event)) {
    console.log(event.message);
  }

  if (isCompleteEvent(event)) {
    return event.data.config;
  }

  if (isErrorEvent(event)) {
    throw new Error(event.error);
  }
}

Preview Events

Some agents emit preview events carrying partial results before the stream completes. Render them to show work-in-progress.
import { isPreviewEvent, isCompleteEvent } from '@graphysdk/agents-sdk';

const stream = await ai.generateMutationStream({
  config,
  userPrompt: 'Group sales by region',
});

for await (const event of stream) {
  if (isPreviewEvent(event)) {
    // Payload shape depends on the agent — mutation previews carry `config`
    console.log('Preview:', event.config);
  }

  if (isCompleteEvent(event)) {
    console.log('Final:', event.data.config);
  }
}
The fields on a preview event depend on the agent — see each agent’s page for what it streams. Events you do not handle can be safely ignored.

Cancellation

Pass an AbortSignal to cancel mid-operation:
import { isCompleteEvent } from '@graphysdk/agents-sdk';

const controller = new AbortController();

const stream = await ai.generateGraphStream({ config, userPrompt: 'Create a complex heatmap' }, controller.signal);

// Cancel from a button click
cancelButton.onclick = () => controller.abort();

try {
  for await (const event of stream) {
    if (isCompleteEvent(event)) {
      return event.data;
    }
  }
} catch (error) {
  if (error instanceof Error && error.name === 'AbortError') {
    console.log('Cancelled by user');
    return;
  }
  throw error;
}

Progress Callback Alternative

If you want progress updates without the streaming API, use the onProgress callback with generateGraph():
const result = await ai.generateGraph({ config, userPrompt: 'Add a trend line' }, (progress) => {
  setStatus(progress.message);
});

// result contains the final GenerateGraphResponse
This collects the stream internally and returns the final result.

React Pattern

Store the abort controller in a ref for proper cleanup:
import { useState, useRef, useEffect } from 'react';
import { GraphyAiSdk, isProgressEvent, isCompleteEvent, isErrorEvent } from '@graphysdk/agents-sdk';
import type { GraphConfig } from '@graphysdk/core';

interface Props {
  initialConfig: GraphConfig;
  onUpdate: (config: GraphConfig) => void;
}

function ChartEditor({ initialConfig, onUpdate }: Props) {
  const [status, setStatus] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const abortRef = useRef<AbortController | null>(null);

  const ai = useRef(
    new GraphyAiSdk({
      apiKey: process.env.NEXT_PUBLIC_GRAPHY_API_KEY,
      baseUrl: 'https://agents.graphy.dev',
    })
  ).current;

  const handleGenerate = async (prompt: string) => {
    // Cancel any in-flight request
    abortRef.current?.abort();
    abortRef.current = new AbortController();

    setIsLoading(true);
    setStatus('');
    setError(null);

    try {
      const stream = await ai.generateGraphStream(
        { config: initialConfig, userPrompt: prompt },
        abortRef.current.signal
      );

      for await (const event of stream) {
        if (isProgressEvent(event)) {
          setStatus(event.message);
        }

        if (isCompleteEvent(event)) {
          onUpdate(event.data.config);
        }

        if (isErrorEvent(event)) {
          setError(event.error);
        }
      }
    } catch (err) {
      if (err instanceof Error && err.name !== 'AbortError') {
        setError(err.message);
      }
    } finally {
      setIsLoading(false);
    }
  };

  // Cleanup on unmount
  useEffect(() => {
    return () => abortRef.current?.abort();
  }, []);

  return (
    <div>
      {isLoading && (
        <div>
          <progress />
          <span>{status}</span>
          <button onClick={() => abortRef.current?.abort()}>Cancel</button>
        </div>
      )}
      {error && <div className="error">{error}</div>}
      <PromptInput onSubmit={handleGenerate} disabled={isLoading} />
    </div>
  );
}
Key patterns:
  1. Cancel previous requests — Abort any in-flight request before starting a new one
  2. Store in ref — The abort controller persists across renders
  3. Cleanup on unmount — Cancel pending requests when the component unmounts
  4. Handle AbortError — Don’t treat user cancellation as an error
ProgressEvent carries a message, not a numeric percentage — show the latest message as a status line alongside an indeterminate progress indicator.

Type Guards

The SDK exports isProgressEvent(), isCompleteEvent(), isErrorEvent(), isPreviewEvent(), and isReasoningEvent() for narrowing SSE events, and isGraphyApiError() for narrowing caught errors. All examples on this page use these type guards. See the Type Reference for full signatures.

Error Handling in Streams

Errors can come from two sources:
  1. Error events — The API returns an error during processing
  2. Exceptions — Network failure, timeout, or abort
import { isErrorEvent, isCompleteEvent, isGraphyApiError } from '@graphysdk/agents-sdk';

try {
  const stream = await ai.generateGraphStream({ config, userPrompt });

  for await (const event of stream) {
    if (isErrorEvent(event)) {
      // API-level error
      if (event.retryable) {
        // Offer retry option
      }
      throw new Error(event.error);
    }

    if (isCompleteEvent(event)) {
      return event.data;
    }
  }
} catch (error) {
  if (error instanceof Error && error.name === 'AbortError') {
    // User cancelled
    return;
  }
  // Network or other error
  console.error('Stream failed:', error);
}
See Error Handling for details on error codes and retry behavior.