---
title: TypeScript
---

import { Callout } from 'fumadocs-ui/components/callout'
import { Card, Cards } from 'fumadocs-ui/components/card'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'

Simの公式TypeScript/JavaScript SDKは完全な型安全性を提供し、Node.jsとブラウザ環境の両方をサポートしています。これにより、Node.jsアプリケーション、Webアプリケーション、その他のJavaScript環境からプログラムでワークフローを実行することができます。

<Callout type="info">
  TypeScript SDKは完全な型安全性、非同期実行サポート、指数バックオフによる自動レート制限、使用状況追跡を提供します。
</Callout>

## インストール

お好みのパッケージマネージャーを使用してSDKをインストールします：

<Tabs items={['npm', 'yarn', 'bun']}>
  <Tab value="npm">

    ```bash
    npm install simstudio-ts-sdk
    ```

  </Tab>
  <Tab value="yarn">

    ```bash
    yarn add simstudio-ts-sdk
    ```

  </Tab>
  <Tab value="bun">

    ```bash
    bun add simstudio-ts-sdk
    ```

  </Tab>
</Tabs>

## クイックスタート

以下は簡単な使用例です：

```typescript
import { SimStudioClient } from 'simstudio-ts-sdk';

// Initialize the client
const client = new SimStudioClient({
  apiKey: 'your-api-key-here',
  baseUrl: 'https://sim.ai' // optional, defaults to https://sim.ai
});

// Execute a workflow
try {
  const result = await client.executeWorkflow('workflow-id');
  console.log('Workflow executed successfully:', result);
} catch (error) {
  console.error('Workflow execution failed:', error);
}
```

## APIリファレンス

### SimStudioClient

#### コンストラクタ

```typescript
new SimStudioClient(config: SimStudioConfig)
```

**設定:**
- `config.apiKey` (string): SimのAPIキー
- `config.baseUrl` (string, オプション): Sim APIのベースURL（デフォルトは `https://sim.ai`）

#### メソッド

##### executeWorkflow()

オプションの入力データでワークフローを実行します。

```typescript
const result = await client.executeWorkflow('workflow-id', {
  input: { message: 'Hello, world!' },
  timeout: 30000 // 30 seconds
});
```

**パラメータ:**
- `workflowId` (string): 実行するワークフローのID
- `options` (ExecutionOptions, オプション):
  - `input` (any): ワークフローに渡す入力データ
  - `timeout` (number): タイムアウト（ミリ秒）（デフォルト: 30000）
  - `stream` (boolean): ストリーミングレスポンスを有効にする（デフォルト: false）
  - `selectedOutputs` (string[]): `blockName.attribute` 形式でストリーミングするブロック出力（例: `["agent1.content"]`）
  - `async` (boolean): 非同期実行（デフォルト: false）

**戻り値:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`

`async: true` の場合、ポーリング用のタスクIDをすぐに返します。それ以外の場合は、完了を待ちます。

##### getWorkflowStatus()

ワークフローのステータス（デプロイメントステータスなど）を取得します。

```typescript
const status = await client.getWorkflowStatus('workflow-id');
console.log('Is deployed:', status.isDeployed);
```

**パラメータ:**
- `workflowId` (string): ワークフローのID

**戻り値:** `Promise<WorkflowStatus>`

##### validateWorkflow()

ワークフローが実行準備ができているかを検証します。

```typescript
const isReady = await client.validateWorkflow('workflow-id');
if (isReady) {
  // Workflow is deployed and ready
}
```

**パラメータ:**
- `workflowId` (string): ワークフローのID

**戻り値:** `Promise<boolean>`

##### getJobStatus()

非同期ジョブ実行のステータスを取得します。

```typescript
const status = await client.getJobStatus('task-id-from-async-execution');
console.log('Status:', status.status); // 'queued', 'processing', 'completed', 'failed'
if (status.status === 'completed') {
  console.log('Output:', status.output);
}
```

**パラメータ:**
- `taskId` (string): 非同期実行から返されたタスクID

**戻り値:** `Promise<JobStatus>`

**レスポンスフィールド:**
- `success` (boolean): リクエストが成功したかどうか
- `taskId` (string): タスクID
- `status` (string): 次のいずれか `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
- `metadata` (object): `startedAt`, `completedAt`, および `duration` を含む
- `output` (any, オプション): ワークフロー出力（完了時）
- `error` (any, オプション): エラー詳細（失敗時）
- `estimatedDuration` (number, オプション): 推定所要時間（ミリ秒）（処理中/キュー時）

##### executeWithRetry()

指数バックオフを使用してレート制限エラー時に自動的に再試行するワークフロー実行。

```typescript
const result = await client.executeWithRetry('workflow-id', {
  input: { message: 'Hello' },
  timeout: 30000
}, {
  maxRetries: 3,           // Maximum number of retries
  initialDelay: 1000,      // Initial delay in ms (1 second)
  maxDelay: 30000,         // Maximum delay in ms (30 seconds)
  backoffMultiplier: 2     // Exponential backoff multiplier
});
```

**パラメータ:**
- `workflowId` (string): 実行するワークフローのID
- `options` (ExecutionOptions, オプション): `executeWorkflow()` と同じ
- `retryOptions` (RetryOptions, オプション):
  - `maxRetries` (number): 最大再試行回数（デフォルト: 3）
  - `initialDelay` (number): 初期遅延（ミリ秒）（デフォルト: 1000）
  - `maxDelay` (number): 最大遅延（ミリ秒）（デフォルト: 30000）
  - `backoffMultiplier` (number): バックオフ乗数（デフォルト: 2）

**戻り値:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`

リトライロジックは指数バックオフ（1秒 → 2秒 → 4秒 → 8秒...）を使用し、サンダリングハード問題を防ぐために±25%のジッターを適用します。APIが `retry-after` ヘッダーを提供する場合、代わりにそれが使用されます。

##### getRateLimitInfo()

最後のAPIレスポンスから現在のレート制限情報を取得します。

```typescript
const rateLimitInfo = client.getRateLimitInfo();
if (rateLimitInfo) {
  console.log('Limit:', rateLimitInfo.limit);
  console.log('Remaining:', rateLimitInfo.remaining);
  console.log('Reset:', new Date(rateLimitInfo.reset * 1000));
}
```

**戻り値:** `RateLimitInfo | null`

##### getUsageLimits()

アカウントの現在の使用制限とクォータ情報を取得します。

```typescript
const limits = await client.getUsageLimits();
console.log('Sync requests remaining:', limits.rateLimit.sync.remaining);
console.log('Async requests remaining:', limits.rateLimit.async.remaining);
console.log('Current period cost:', limits.usage.currentPeriodCost);
console.log('Plan:', limits.usage.plan);
```

**戻り値:** `Promise<UsageLimits>`

**レスポンス構造:**

```typescript
{
  success: boolean
  rateLimit: {
    sync: {
      isLimited: boolean
      limit: number
      remaining: number
      resetAt: string
    }
    async: {
      isLimited: boolean
      limit: number
      remaining: number
      resetAt: string
    }
    authType: string  // 'api' or 'manual'
  }
  usage: {
    currentPeriodCost: number
    limit: number
    plan: string  // e.g., 'free', 'pro'
  }
}
```

##### setApiKey()

APIキーを更新します。

```typescript
client.setApiKey('new-api-key');
```

##### setBaseUrl()

ベースURLを更新します。

```typescript
client.setBaseUrl('https://my-custom-domain.com');
```

## 型定義

### WorkflowExecutionResult

```typescript
interface WorkflowExecutionResult {
  success: boolean;
  output?: any;
  error?: string;
  logs?: any[];
  metadata?: {
    duration?: number;
    executionId?: string;
    [key: string]: any;
  };
  traceSpans?: any[];
  totalDuration?: number;
}
```

### AsyncExecutionResult

```typescript
interface AsyncExecutionResult {
  success: boolean;
  taskId: string;
  status: 'queued';
  createdAt: string;
  links: {
    status: string;  // e.g., "/api/jobs/{taskId}"
  };
}
```

### WorkflowStatus

```typescript
interface WorkflowStatus {
  isDeployed: boolean;
  deployedAt?: string;
  isPublished: boolean;
  needsRedeployment: boolean;
}
```

### レート制限情報

```typescript
interface RateLimitInfo {
  limit: number;
  remaining: number;
  reset: number;
  retryAfter?: number;
}
```

### 使用制限

```typescript
interface UsageLimits {
  success: boolean;
  rateLimit: {
    sync: {
      isLimited: boolean;
      limit: number;
      remaining: number;
      resetAt: string;
    };
    async: {
      isLimited: boolean;
      limit: number;
      remaining: number;
      resetAt: string;
    };
    authType: string;
  };
  usage: {
    currentPeriodCost: number;
    limit: number;
    plan: string;
  };
}
```

### SimStudioエラー

```typescript
class SimStudioError extends Error {
  code?: string;
  status?: number;
}
```

**一般的なエラーコード:**
- `UNAUTHORIZED`: 無効なAPIキー
- `TIMEOUT`: リクエストタイムアウト
- `RATE_LIMIT_EXCEEDED`: レート制限超過
- `USAGE_LIMIT_EXCEEDED`: 使用制限超過
- `EXECUTION_ERROR`: ワークフロー実行失敗

## 例

### 基本的なワークフロー実行

<Steps>
  <Step title="クライアントの初期化">
    APIキーを使用してSimStudioClientをセットアップします。
  </Step>
  <Step title="ワークフローの検証">
    ワークフローがデプロイされ、実行準備ができているか確認します。
  </Step>
  <Step title="ワークフローの実行">
    入力データでワークフローを実行します。
  </Step>
  <Step title="結果の処理">
    実行結果を処理し、エラーを適切に扱います。
  </Step>
</Steps>

```typescript
import { SimStudioClient } from 'simstudio-ts-sdk';

const client = new SimStudioClient({
  apiKey: process.env.SIM_API_KEY!
});

async function runWorkflow() {
  try {
    // Check if workflow is ready
    const isReady = await client.validateWorkflow('my-workflow-id');
    if (!isReady) {
      throw new Error('Workflow is not deployed or ready');
    }

    // Execute the workflow
    const result = await client.executeWorkflow('my-workflow-id', {
      input: {
        message: 'Process this data',
        userId: '12345'
      }
    });

    if (result.success) {
      console.log('Output:', result.output);
      console.log('Duration:', result.metadata?.duration);
    } else {
      console.error('Workflow failed:', result.error);
    }
  } catch (error) {
    console.error('Error:', error);
  }
}

runWorkflow();
```

### エラー処理

ワークフロー実行中に発生する可能性のある様々なタイプのエラーを処理します：

```typescript
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';

const client = new SimStudioClient({
  apiKey: process.env.SIM_API_KEY!
});

async function executeWithErrorHandling() {
  try {
    const result = await client.executeWorkflow('workflow-id');
    return result;
  } catch (error) {
    if (error instanceof SimStudioError) {
      switch (error.code) {
        case 'UNAUTHORIZED':
          console.error('Invalid API key');
          break;
        case 'TIMEOUT':
          console.error('Workflow execution timed out');
          break;
        case 'USAGE_LIMIT_EXCEEDED':
          console.error('Usage limit exceeded');
          break;
        case 'INVALID_JSON':
          console.error('Invalid JSON in request body');
          break;
        default:
          console.error('Workflow error:', error.message);
      }
    } else {
      console.error('Unexpected error:', error);
    }
    throw error;
  }
}
```

### 環境設定

環境変数を使用してクライアントを設定します：

<Tabs items={['開発環境', '本番環境']}>
  <Tab value="開発環境">

    ```typescript
    import { SimStudioClient } from 'simstudio-ts-sdk';

    // Development configuration
    const apiKey = process.env.SIM_API_KEY;
    if (!apiKey) {
      throw new Error('SIM_API_KEY environment variable is required');
    }

    const client = new SimStudioClient({
      apiKey,
      baseUrl: process.env.SIM_BASE_URL // optional
    });
    ```

  </Tab>
  <Tab value="本番環境">

    ```typescript
    import { SimStudioClient } from 'simstudio-ts-sdk';

    // Production configuration with validation
    const apiKey = process.env.SIM_API_KEY;
    if (!apiKey) {
      throw new Error('SIM_API_KEY environment variable is required');
    }

    const client = new SimStudioClient({
      apiKey,
      baseUrl: process.env.SIM_BASE_URL || 'https://sim.ai'
    });
    ```

  </Tab>
</Tabs>

### Node.js Expressとの統合

Express.jsサーバーとの統合：

```typescript
import express from 'express';
import { SimStudioClient } from 'simstudio-ts-sdk';

const app = express();
const client = new SimStudioClient({
  apiKey: process.env.SIM_API_KEY!
});

app.use(express.json());

app.post('/execute-workflow', async (req, res) => {
  try {
    const { workflowId, input } = req.body;
    
    const result = await client.executeWorkflow(workflowId, {
      input,
      timeout: 60000
    });

    res.json({
      success: true,
      data: result
    });
  } catch (error) {
    console.error('Workflow execution error:', error);
    res.status(500).json({
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error'
    });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
```

### Next.js APIルート

Next.js APIルートでの使用方法：

```typescript
// pages/api/workflow.ts or app/api/workflow/route.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { SimStudioClient } from 'simstudio-ts-sdk';

const client = new SimStudioClient({
  apiKey: process.env.SIM_API_KEY!
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const { workflowId, input } = req.body;

    const result = await client.executeWorkflow(workflowId, {
      input,
      timeout: 30000
    });

    res.status(200).json(result);
  } catch (error) {
    console.error('Error executing workflow:', error);
    res.status(500).json({
      error: 'Failed to execute workflow'
    });
  }
}
```

### ブラウザでの使用

ブラウザでの使用方法（適切なCORS設定が必要）：

```typescript
import { SimStudioClient } from 'simstudio-ts-sdk';

// Note: In production, use a proxy server to avoid exposing API keys
const client = new SimStudioClient({
  apiKey: 'your-public-api-key', // Use with caution in browser
  baseUrl: 'https://sim.ai'
});

async function executeClientSideWorkflow() {
  try {
    const result = await client.executeWorkflow('workflow-id', {
      input: {
        userInput: 'Hello from browser'
      }
    });

    console.log('Workflow result:', result);

    // Update UI with result
    document.getElementById('result')!.textContent =
      JSON.stringify(result.output, null, 2);
  } catch (error) {
    console.error('Error:', error);
  }
}
```

### ファイルアップロード

Fileオブジェクトは自動的に検出され、base64形式に変換されます。ワークフローのAPIトリガー入力形式に一致するフィールド名で入力に含めてください。

SDKはFileオブジェクトを以下の形式に変換します：

```typescript
{
  type: 'file',
  data: 'data:mime/type;base64,base64data',
  name: 'filename',
  mime: 'mime/type'
}
```

または、URL形式を使用して手動でファイルを提供することもできます：

```typescript
{
  type: 'url',
  data: 'https://example.com/file.pdf',
  name: 'file.pdf',
  mime: 'application/pdf'
}
```

<Tabs items={['Browser', 'Node.js']}>
  <Tab value="Browser">

    ```typescript
    import { SimStudioClient } from 'simstudio-ts-sdk';

    const client = new SimStudioClient({
      apiKey: process.env.NEXT_PUBLIC_SIM_API_KEY!
    });

    // From file input
    async function handleFileUpload(event: Event) {
      const input = event.target as HTMLInputElement;
      const files = Array.from(input.files || []);

      // Include files under the field name from your API trigger's input format
      const result = await client.executeWorkflow('workflow-id', {
        input: {
          documents: files,  // Must match your workflow's "files" field name
          instructions: 'Analyze these documents'
        }
      });

      console.log('Result:', result);
    }
    ```

  </Tab>
  <Tab value="Node.js">

    ```typescript
    import { SimStudioClient } from 'simstudio-ts-sdk';
    import fs from 'fs';

    const client = new SimStudioClient({
      apiKey: process.env.SIM_API_KEY!
    });

    // Read file and create File object
    const fileBuffer = fs.readFileSync('./document.pdf');
    const file = new File([fileBuffer], 'document.pdf', {
      type: 'application/pdf'
    });

    // Include files under the field name from your API trigger's input format
    const result = await client.executeWorkflow('workflow-id', {
      input: {
        documents: [file],  // Must match your workflow's "files" field name
        query: 'Summarize this document'
      }
    });
    ```

  </Tab>
</Tabs>

<Callout type="warning">
  ブラウザでSDKを使用する場合、機密性の高いAPIキーを公開しないよう注意してください。バックエンドプロキシや権限が制限された公開APIキーの使用を検討してください。
</Callout>

### Reactフックの例

ワークフロー実行用のカスタムReactフックを作成する：

```typescript
import { useState, useCallback } from 'react';
import { SimStudioClient, WorkflowExecutionResult } from 'simstudio-ts-sdk';

const client = new SimStudioClient({
  apiKey: process.env.SIM_API_KEY!
});

interface UseWorkflowResult {
  result: WorkflowExecutionResult | null;
  loading: boolean;
  error: Error | null;
  executeWorkflow: (workflowId: string, input?: any) => Promise<void>;
}

export function useWorkflow(): UseWorkflowResult {
  const [result, setResult] = useState<WorkflowExecutionResult | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const executeWorkflow = useCallback(async (workflowId: string, input?: any) => {
    setLoading(true);
    setError(null);
    setResult(null);

    try {
      const workflowResult = await client.executeWorkflow(workflowId, {
        input,
        timeout: 30000
      });
      setResult(workflowResult);
    } catch (err) {
      setError(err instanceof Error ? err : new Error('Unknown error'));
    } finally {
      setLoading(false);
    }
  }, []);

  return {
    result,
    loading,
    error,
    executeWorkflow
  };
}

// Usage in component
function WorkflowComponent() {
  const { result, loading, error, executeWorkflow } = useWorkflow();

  const handleExecute = () => {
    executeWorkflow('my-workflow-id', {
      message: 'Hello from React!'
    });
  };

  return (
    <div>
      <button onClick={handleExecute} disabled={loading}>
        {loading ? 'Executing...' : 'Execute Workflow'}
      </button>

      {error && <div>Error: {error.message}</div>}
      {result && (
        <div>
          <h3>Result:</h3>
          <pre>{JSON.stringify(result, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}
```

### 非同期ワークフロー実行

長時間実行されるタスクのためにワークフローを非同期で実行する：

```typescript
import { SimStudioClient, AsyncExecutionResult } from 'simstudio-ts-sdk';

const client = new SimStudioClient({
  apiKey: process.env.SIM_API_KEY!
});

async function executeAsync() {
  try {
    // Start async execution
    const result = await client.executeWorkflow('workflow-id', {
      input: { data: 'large dataset' },
      async: true  // Execute asynchronously
    });

    // Check if result is an async execution
    if ('taskId' in result) {
      console.log('Task ID:', result.taskId);
      console.log('Status endpoint:', result.links.status);

      // Poll for completion
      let status = await client.getJobStatus(result.taskId);

      while (status.status === 'queued' || status.status === 'processing') {
        console.log('Current status:', status.status);
        await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
        status = await client.getJobStatus(result.taskId);
      }

      if (status.status === 'completed') {
        console.log('Workflow completed!');
        console.log('Output:', status.output);
        console.log('Duration:', status.metadata.duration);
      } else {
        console.error('Workflow failed:', status.error);
      }
    }
  } catch (error) {
    console.error('Error:', error);
  }
}

executeAsync();
```

### レート制限とリトライ

指数バックオフを使用して自動的にレート制限を処理する：

```typescript
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';

const client = new SimStudioClient({
  apiKey: process.env.SIM_API_KEY!
});

async function executeWithRetryHandling() {
  try {
    // Automatically retries on rate limit
    const result = await client.executeWithRetry('workflow-id', {
      input: { message: 'Process this' }
    }, {
      maxRetries: 5,
      initialDelay: 1000,
      maxDelay: 60000,
      backoffMultiplier: 2
    });

    console.log('Success:', result);
  } catch (error) {
    if (error instanceof SimStudioError && error.code === 'RATE_LIMIT_EXCEEDED') {
      console.error('Rate limit exceeded after all retries');

      // Check rate limit info
      const rateLimitInfo = client.getRateLimitInfo();
      if (rateLimitInfo) {
        console.log('Rate limit resets at:', new Date(rateLimitInfo.reset * 1000));
      }
    }
  }
}
```

### 使用状況のモニタリング

アカウントの使用状況と制限をモニタリングします：

```typescript
import { SimStudioClient } from 'simstudio-ts-sdk';

const client = new SimStudioClient({
  apiKey: process.env.SIM_API_KEY!
});

async function checkUsage() {
  try {
    const limits = await client.getUsageLimits();

    console.log('=== Rate Limits ===');
    console.log('Sync requests:');
    console.log('  Limit:', limits.rateLimit.sync.limit);
    console.log('  Remaining:', limits.rateLimit.sync.remaining);
    console.log('  Resets at:', limits.rateLimit.sync.resetAt);
    console.log('  Is limited:', limits.rateLimit.sync.isLimited);

    console.log('\nAsync requests:');
    console.log('  Limit:', limits.rateLimit.async.limit);
    console.log('  Remaining:', limits.rateLimit.async.remaining);
    console.log('  Resets at:', limits.rateLimit.async.resetAt);
    console.log('  Is limited:', limits.rateLimit.async.isLimited);

    console.log('\n=== Usage ===');
    console.log('Current period cost: $' + limits.usage.currentPeriodCost.toFixed(2));
    console.log('Limit: $' + limits.usage.limit.toFixed(2));
    console.log('Plan:', limits.usage.plan);

    const percentUsed = (limits.usage.currentPeriodCost / limits.usage.limit) * 100;
    console.log('Usage: ' + percentUsed.toFixed(1) + '%');

    if (percentUsed > 80) {
      console.warn('⚠️  Warning: You are approaching your usage limit!');
    }
  } catch (error) {
    console.error('Error checking usage:', error);
  }
}

checkUsage();
```

### ワークフローのストリーミング実行

リアルタイムのストリーミングレスポンスでワークフローを実行します：

```typescript
import { SimStudioClient } from 'simstudio-ts-sdk';

const client = new SimStudioClient({
  apiKey: process.env.SIM_API_KEY!
});

async function executeWithStreaming() {
  try {
    // Enable streaming for specific block outputs
    const result = await client.executeWorkflow('workflow-id', {
      input: { message: 'Count to five' },
      stream: true,
      selectedOutputs: ['agent1.content'] // Use blockName.attribute format
    });

    console.log('Workflow result:', result);
  } catch (error) {
    console.error('Error:', error);
  }
}
```

ストリーミングレスポンスはServer-Sent Events（SSE）形式に従います：

```
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}

data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}

data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}

data: [DONE]
```

**Reactストリーミングの例：**

```typescript
import { useState, useEffect } from 'react';

function StreamingWorkflow() {
  const [output, setOutput] = useState('');
  const [loading, setLoading] = useState(false);

  const executeStreaming = async () => {
    setLoading(true);
    setOutput('');

    // IMPORTANT: Make this API call from your backend server, not the browser
    // Never expose your API key in client-side code
    const response = await fetch('https://sim.ai/api/workflows/WORKFLOW_ID/execute', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': process.env.SIM_API_KEY! // Server-side environment variable only
      },
      body: JSON.stringify({
        message: 'Generate a story',
        stream: true,
        selectedOutputs: ['agent1.content']
      })
    });

    const reader = response.body?.getReader();
    const decoder = new TextDecoder();

    while (reader) {
      const { done, value } = await reader.read();
      if (done) break;

      const chunk = decoder.decode(value);
      const lines = chunk.split('\n\n');

      for (const line of lines) {
        if (line.startsWith('data: ')) {
          const data = line.slice(6);
          if (data === '[DONE]') {
            setLoading(false);
            break;
          }

          try {
            const parsed = JSON.parse(data);
            if (parsed.chunk) {
              setOutput(prev => prev + parsed.chunk);
            } else if (parsed.event === 'done') {
              console.log('Execution complete:', parsed.metadata);
            }
          } catch (e) {
            // Skip invalid JSON
          }
        }
      }
    }
  };

  return (
    <div>
      <button onClick={executeStreaming} disabled={loading}>
        {loading ? 'Generating...' : 'Start Streaming'}
      </button>
      <div style={{ whiteSpace: 'pre-wrap' }}>{output}</div>
    </div>
  );
}
```

## APIキーの取得

<Steps>
  <Step title="Simにログイン">
    [Sim](https://sim.ai)に移動してアカウントにログインします。
  </Step>
  <Step title="ワークフローを開く">
    プログラムで実行したいワークフローに移動します。
  </Step>
  <Step title="ワークフローをデプロイする">
    まだデプロイされていない場合は、「デプロイ」をクリックしてワークフローをデプロイします。
  </Step>
  <Step title="APIキーを作成または選択する">
    デプロイプロセス中に、APIキーを選択または作成します。
  </Step>
  <Step title="APIキーをコピーする">
    TypeScript/JavaScriptアプリケーションで使用するAPIキーをコピーします。
  </Step>
</Steps>

## 要件

- Node.js 16+
- TypeScript 5.0+（TypeScriptプロジェクト用）

## ライセンス

Apache-2.0
