> ## Documentation Index
> Fetch the complete documentation index at: https://docs.graphy.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Streaming

AI operations take time. Streaming shows real-time progress as the agent works.

## Event Types

```typescript theme={null}
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.

```typescript theme={null}
interface ProgressEvent {
  type: 'progress';
  message: string;
  agentId?: string;
  executionId?: string;
  iteration?: number;
  metadata?: Record<string, unknown>;
}
```

### CompleteEvent

```typescript theme={null}
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](/agents/reference/types#generategraphresponse).

### ErrorEvent

```typescript theme={null}
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](/agents/sdk/mutate) previews carry `config`.

```typescript theme={null}
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.

```typescript theme={null}
interface ReasoningEvent {
  type: 'reasoning';
  agentId: string;
  executionId: string;
  message: string;
}
```

***

## Async Iterator Pattern

`generateGraphStream()` returns an async iterator:

```typescript theme={null}
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.

```typescript theme={null}
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:

```typescript theme={null}
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()`:

```typescript theme={null}
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:

```tsx theme={null}
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](/agents/reference/types) 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

```typescript theme={null}
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](/agents/sdk/errors) for details on error codes and retry behavior.
