---
name: openai-assistants
description: |
  Build stateful chatbots with OpenAI Assistants API v2 - Code Interpreter, File Search (10k files), Function Calling. ⚠️ Sunset H1 2026; use openai-responses for new projects.

  Use when: building stateful chatbots, implementing RAG, or troubleshooting "thread has active run", vector store delays, polling timeouts, or file upload errors.
license: MIT
---

# OpenAI Assistants API v2

**Status**: Production Ready (Deprecated H1 2026)
**Package**: openai@6.7.0
**Last Updated**: 2025-10-25
**v1 Deprecated**: December 18, 2024
**v2 Sunset**: H1 2026 (migrate to Responses API)

---

## ⚠️ Important: Deprecation Notice

**OpenAI announced that the Assistants API will be deprecated in favor of the [Responses API](../openai-responses/SKILL.md).**

**Timeline:**
- ✅ **Dec 18, 2024**: Assistants API v1 deprecated
- ⏳ **H1 2026**: Planned sunset of Assistants API v2
- ✅ **Now**: Responses API available (recommended for new projects)

**Should you still use this skill?**
- ✅ **Yes, if**: You have existing Assistants API code (12-18 month migration window)
- ✅ **Yes, if**: You need to maintain legacy applications
- ✅ **Yes, if**: Planning migration from Assistants → Responses
- ❌ **No, if**: Starting a new project (use openai-responses skill instead)

**Migration Path:**
See `references/migration-to-responses.md` for complete migration guide.

---

## Table of Contents

1. [Quick Start](#quick-start)
2. [Core Concepts](#core-concepts)
3. [Assistants](#assistants)
4. [Threads](#threads)
5. [Messages](#messages)
6. [Runs](#runs)
7. [Streaming Runs](#streaming-runs)
8. [Tools](#tools)
   - [Code Interpreter](#code-interpreter)
   - [File Search](#file-search)
   - [Function Calling](#function-calling)
9. [Vector Stores](#vector-stores)
10. [File Uploads](#file-uploads)
11. [Thread Lifecycle Management](#thread-lifecycle-management)
12. [Error Handling](#error-handling)
13. [Production Best Practices](#production-best-practices)
14. [Relationship to Other Skills](#relationship-to-other-skills)

---

## Quick Start

### Installation

```bash
npm install openai@6.7.0
```

### Environment Setup

```bash
export OPENAI_API_KEY="sk-..."
```

### Basic Assistant (Node.js SDK)

```typescript
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

// 1. Create an assistant
const assistant = await openai.beta.assistants.create({
  name: "Math Tutor",
  instructions: "You are a personal math tutor. Write and run code to answer math questions.",
  tools: [{ type: "code_interpreter" }],
  model: "gpt-4o",
});

// 2. Create a thread
const thread = await openai.beta.threads.create();

// 3. Add a message to the thread
await openai.beta.threads.messages.create(thread.id, {
  role: "user",
  content: "I need to solve the equation `3x + 11 = 14`. Can you help me?",
});

// 4. Create a run
const run = await openai.beta.threads.runs.create(thread.id, {
  assistant_id: assistant.id,
});

// 5. Poll for completion
let runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);

while (runStatus.status !== 'completed') {
  await new Promise(resolve => setTimeout(resolve, 1000));
  runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
}

// 6. Retrieve messages
const messages = await openai.beta.threads.messages.list(thread.id);
console.log(messages.data[0].content[0].text.value);
```

### Basic Assistant (Fetch - Cloudflare Workers)

```typescript
// 1. Create assistant
const assistant = await fetch('https://api.openai.com/v1/assistants', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
    'Content-Type': 'application/json',
    'OpenAI-Beta': 'assistants=v2',
  },
  body: JSON.stringify({
    name: "Math Tutor",
    instructions: "You are a helpful math tutor.",
    model: "gpt-4o",
  }),
});

const assistantData = await assistant.json();

// 2. Create thread
const thread = await fetch('https://api.openai.com/v1/threads', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
    'Content-Type': 'application/json',
    'OpenAI-Beta': 'assistants=v2',
  },
});

const threadData = await thread.json();

// 3. Add message and create run
const run = await fetch(`https://api.openai.com/v1/threads/${threadData.id}/runs`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
    'Content-Type': 'application/json',
    'OpenAI-Beta': 'assistants=v2',
  },
  body: JSON.stringify({
    assistant_id: assistantData.id,
    additional_messages: [{
      role: "user",
      content: "What is 3x + 11 = 14?",
    }],
  }),
});

// Poll for completion...
```

---

## Core Concepts

The Assistants API uses four main objects:

### 1. **Assistants**
Configured AI entities with:
- Instructions (system prompt, max 256k characters)
- Model (gpt-4o, gpt-5, etc.)
- Tools (Code Interpreter, File Search, Functions)
- File attachments
- Metadata

### 2. **Threads**
Conversation containers that:
- Store message history
- Persist across runs
- Can have metadata
- Support up to 100,000 messages

### 3. **Messages**
Individual messages in a thread:
- User messages (input)
- Assistant messages (output)
- Can include file attachments
- Support text and image content

### 4. **Runs**
Execution of an assistant on a thread:
- Asynchronous processing
- Multiple states (queued, in_progress, completed, failed, etc.)
- Can stream results
- Handle tool calls automatically

---

## Assistants

### Create an Assistant

```typescript
const assistant = await openai.beta.assistants.create({
  name: "Data Analyst",
  instructions: "You are a data analyst. Use code interpreter to analyze data and create visualizations.",
  model: "gpt-4o",
  tools: [
    { type: "code_interpreter" },
    { type: "file_search" },
  ],
  tool_resources: {
    file_search: {
      vector_store_ids: ["vs_abc123"],
    },
  },
  metadata: {
    department: "analytics",
    version: "1.0",
  },
});
```

**Parameters:**
- `model` (required): Model ID (gpt-4o, gpt-5, gpt-4-turbo)
- `instructions`: System prompt (max 256k characters in v2, was 32k in v1)
- `name`: Assistant name (max 256 characters)
- `description`: Description (max 512 characters)
- `tools`: Array of tools (max 128 tools)
- `tool_resources`: Resources for tools (vector stores, files)
- `temperature`: 0-2 (default 1)
- `top_p`: 0-1 (default 1)
- `response_format`: "auto", "json_object", or JSON schema
- `metadata`: Key-value pairs (max 16 pairs)

### Retrieve an Assistant

```typescript
const assistant = await openai.beta.assistants.retrieve("asst_abc123");
```

### Update an Assistant

```typescript
const updatedAssistant = await openai.beta.assistants.update("asst_abc123", {
  instructions: "Updated instructions",
  tools: [{ type: "code_interpreter" }, { type: "file_search" }],
});
```

### Delete an Assistant

```typescript
await openai.beta.assistants.del("asst_abc123");
```

### List Assistants

```typescript
const assistants = await openai.beta.assistants.list({
  limit: 20,
  order: "desc",
});
```

---

## Threads

Threads store conversation history and persist across runs.

### Create a Thread

```typescript
// Empty thread
const thread = await openai.beta.threads.create();

// Thread with initial messages
const thread = await openai.beta.threads.create({
  messages: [
    {
      role: "user",
      content: "Hello! I need help with Python.",
      metadata: { source: "web" },
    },
  ],
  metadata: {
    user_id: "user_123",
    session_id: "session_456",
  },
});
```

### Retrieve a Thread

```typescript
const thread = await openai.beta.threads.retrieve("thread_abc123");
```

### Update Thread Metadata

```typescript
const thread = await openai.beta.threads.update("thread_abc123", {
  metadata: {
    user_id: "user_123",
    last_active: new Date().toISOString(),
  },
});
```

### Delete a Thread

```typescript
await openai.beta.threads.del("thread_abc123");
```

**⚠️ Warning**: Deleting a thread also deletes all messages and runs. Cannot be undone.

---

## Messages

### Add a Message to a Thread

```typescript
const message = await openai.beta.threads.messages.create("thread_abc123", {
  role: "user",
  content: "Can you analyze this data?",
  attachments: [
    {
      file_id: "file_abc123",
      tools: [{ type: "code_interpreter" }],
    },
  ],
  metadata: {
    timestamp: new Date().toISOString(),
  },
});
```

**Parameters:**
- `role`: "user" only (assistant messages created by runs)
- `content`: Text or array of content blocks
- `attachments`: Files with associated tools
- `metadata`: Key-value pairs

### Retrieve a Message

```typescript
const message = await openai.beta.threads.messages.retrieve(
  "thread_abc123",
  "msg_abc123"
);
```

### List Messages

```typescript
const messages = await openai.beta.threads.messages.list("thread_abc123", {
  limit: 20,
  order: "desc", // "asc" or "desc"
});

// Iterate through messages
for (const message of messages.data) {
  console.log(`${message.role}: ${message.content[0].text.value}`);
}
```

### Update Message Metadata

```typescript
const message = await openai.beta.threads.messages.update(
  "thread_abc123",
  "msg_abc123",
  {
    metadata: {
      edited: "true",
      edit_timestamp: new Date().toISOString(),
    },
  }
);
```

### Delete a Message

```typescript
await openai.beta.threads.messages.del("thread_abc123", "msg_abc123");
```

---

## Runs

Runs execute an assistant on a thread.

### Create a Run

```typescript
const run = await openai.beta.threads.runs.create("thread_abc123", {
  assistant_id: "asst_abc123",
  instructions: "Please address the user as Jane Doe.",
  additional_messages: [
    {
      role: "user",
      content: "Can you help me with this?",
    },
  ],
});
```

**Parameters:**
- `assistant_id` (required): Assistant to use
- `instructions`: Override assistant instructions
- `additional_messages`: Add messages before running
- `tools`: Override assistant tools
- `metadata`: Key-value pairs
- `temperature`: Override temperature
- `top_p`: Override top_p
- `max_prompt_tokens`: Limit input tokens
- `max_completion_tokens`: Limit output tokens

### Retrieve a Run

```typescript
const run = await openai.beta.threads.runs.retrieve(
  "thread_abc123",
  "run_abc123"
);

console.log(run.status); // queued, in_progress, requires_action, completed, failed, etc.
```

### Run States

| State | Description |
|-------|-------------|
| `queued` | Run is waiting to start |
| `in_progress` | Run is executing |
| `requires_action` | Function calling needs your input |
| `cancelling` | Cancellation in progress |
| `cancelled` | Run was cancelled |
| `failed` | Run failed (check `last_error`) |
| `completed` | Run finished successfully |
| `expired` | Run expired (max 10 minutes) |

### Polling Pattern

```typescript
async function pollRunCompletion(threadId: string, runId: string) {
  let run = await openai.beta.threads.runs.retrieve(threadId, runId);

  while (['queued', 'in_progress', 'cancelling'].includes(run.status)) {
    await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second
    run = await openai.beta.threads.runs.retrieve(threadId, runId);
  }

  if (run.status === 'failed') {
    throw new Error(`Run failed: ${run.last_error?.message}`);
  }

  if (run.status === 'requires_action') {
    // Handle function calling (see Function Calling section)
    return run;
  }

  return run; // completed
}

const run = await openai.beta.threads.runs.create(threadId, { assistant_id: assistantId });
const completedRun = await pollRunCompletion(threadId, run.id);
```

### Cancel a Run

```typescript
const run = await openai.beta.threads.runs.cancel("thread_abc123", "run_abc123");
```

**⚠️ Important**: Cancellation is asynchronous. Check `status` becomes `cancelled`.

### List Runs

```typescript
const runs = await openai.beta.threads.runs.list("thread_abc123", {
  limit: 10,
  order: "desc",
});
```

---

## Streaming Runs

Stream run events in real-time using Server-Sent Events (SSE).

### Basic Streaming

```typescript
const stream = await openai.beta.threads.runs.stream("thread_abc123", {
  assistant_id: "asst_abc123",
});

for await (const event of stream) {
  if (event.event === 'thread.message.delta') {
    const delta = event.data.delta.content?.[0]?.text?.value;
    if (delta) {
      process.stdout.write(delta);
    }
  }
}
```

### Stream Event Types

| Event | Description |
|-------|-------------|
| `thread.run.created` | Run was created |
| `thread.run.in_progress` | Run started |
| `thread.run.step.created` | Step created (tool call, message creation) |
| `thread.run.step.delta` | Step progress update |
| `thread.message.created` | Message created |
| `thread.message.delta` | Message content streaming |
| `thread.message.completed` | Message finished |
| `thread.run.completed` | Run finished |
| `thread.run.failed` | Run failed |
| `thread.run.requires_action` | Function calling needed |

### Complete Streaming Example

```typescript
async function streamAssistantResponse(threadId: string, assistantId: string) {
  const stream = await openai.beta.threads.runs.stream(threadId, {
    assistant_id: assistantId,
  });

  for await (const event of stream) {
    switch (event.event) {
      case 'thread.run.created':
        console.log('\\nRun started...');
        break;

      case 'thread.message.delta':
        const delta = event.data.delta.content?.[0];
        if (delta?.type === 'text' && delta.text?.value) {
          process.stdout.write(delta.text.value);
        }
        break;

      case 'thread.run.step.delta':
        const toolCall = event.data.delta.step_details;
        if (toolCall?.type === 'tool_calls') {
          const codeInterpreter = toolCall.tool_calls?.[0]?.code_interpreter;
          if (codeInterpreter?.input) {
            console.log('\\nExecuting code:', codeInterpreter.input);
          }
        }
        break;

      case 'thread.run.completed':
        console.log('\\n\\nRun completed!');
        break;

      case 'thread.run.failed':
        console.error('\\nRun failed:', event.data.last_error);
        break;

      case 'thread.run.requires_action':
        // Handle function calling
        console.log('\\nFunction calling required');
        break;
    }
  }
}
```

---

## Tools

Assistants API supports three types of tools:

### Code Interpreter

Executes Python code in a sandboxed environment.

**Capabilities:**
- Run Python code
- Generate charts/graphs
- Process files (CSV, JSON, text, images, etc.)
- Return file outputs (images, data files)
- Install packages (limited set available)

**Example:**

```typescript
const assistant = await openai.beta.assistants.create({
  name: "Data Analyst",
  instructions: "You are a data analyst. Use Python to analyze data and create visualizations.",
  model: "gpt-4o",
  tools: [{ type: "code_interpreter" }],
});

// Upload a file
const file = await openai.files.create({
  file: fs.createReadStream("sales_data.csv"),
  purpose: "assistants",
});

// Create thread with file
const thread = await openai.beta.threads.create({
  messages: [{
    role: "user",
    content: "Analyze this sales data and create a visualization.",
    attachments: [{
      file_id: file.id,
      tools: [{ type: "code_interpreter" }],
    }],
  }],
});

// Run
const run = await openai.beta.threads.runs.create(thread.id, {
  assistant_id: assistant.id,
});

// Poll for completion and retrieve outputs
```

**Output Files:**

Code Interpreter can generate files (images, CSVs, etc.). Access them via:

```typescript
const messages = await openai.beta.threads.messages.list(thread.id);
const message = messages.data[0];

for (const content of message.content) {
  if (content.type === 'image_file') {
    const fileId = content.image_file.file_id;
    const fileContent = await openai.files.content(fileId);
    // Save or process file
  }
}
```

### File Search

Semantic search over uploaded documents using vector stores.

**Key Features:**
- Up to 10,000 files per assistant (500x more than v1)
- Automatic chunking and embedding
- Vector + keyword search
- Parallel queries with multi-threading
- Advanced reranking

**Pricing:**
- $0.10/GB/day for vector storage
- First 1GB free

**Example:**

```typescript
// 1. Create vector store
const vectorStore = await openai.beta.vectorStores.create({
  name: "Product Documentation",
  metadata: { category: "docs" },
});

// 2. Upload files to vector store
const file = await openai.files.create({
  file: fs.createReadStream("product_guide.pdf"),
  purpose: "assistants",
});

await openai.beta.vectorStores.files.create(vectorStore.id, {
  file_id: file.id,
});

// 3. Create assistant with file search
const assistant = await openai.beta.assistants.create({
  name: "Product Support",
  instructions: "Use file search to answer questions about our products.",
  model: "gpt-4o",
  tools: [{ type: "file_search" }],
  tool_resources: {
    file_search: {
      vector_store_ids: [vectorStore.id],
    },
  },
});

// 4. Create thread and run
const thread = await openai.beta.threads.create({
  messages: [{
    role: "user",
    content: "How do I install the product?",
  }],
});

const run = await openai.beta.threads.runs.create(thread.id, {
  assistant_id: assistant.id,
});
```

**Best Practices:**
- Wait for vector store status to be `completed` before using
- Use metadata for filtering (coming soon)
- Chunk large documents appropriately
- Monitor storage costs

### Function Calling

Define custom functions for the assistant to call.

**Example:**

```typescript
const assistant = await openai.beta.assistants.create({
  name: "Weather Assistant",
  instructions: "You help users get weather information.",
  model: "gpt-4o",
  tools: [{
    type: "function",
    function: {
      name: "get_weather",
      description: "Get the current weather for a location",
      parameters: {
        type: "object",
        properties: {
          location: {
            type: "string",
            description: "City name, e.g., 'San Francisco'",
          },
          unit: {
            type: "string",
            enum: ["celsius", "fahrenheit"],
            description: "Temperature unit",
          },
        },
        required: ["location"],
      },
    },
  }],
});

// Create thread and run
const thread = await openai.beta.threads.create({
  messages: [{
    role: "user",
    content: "What's the weather in San Francisco?",
  }],
});

let run = await openai.beta.threads.runs.create(thread.id, {
  assistant_id: assistant.id,
});

// Poll until requires_action
while (run.status === 'in_progress' || run.status === 'queued') {
  await new Promise(resolve => setTimeout(resolve, 1000));
  run = await openai.beta.threads.runs.retrieve(thread.id, run.id);
}

if (run.status === 'requires_action') {
  const toolCalls = run.required_action.submit_tool_outputs.tool_calls;

  const toolOutputs = [];
  for (const toolCall of toolCalls) {
    if (toolCall.function.name === 'get_weather') {
      const args = JSON.parse(toolCall.function.arguments);
      // Call your actual weather API
      const weather = await getWeatherAPI(args.location, args.unit);

      toolOutputs.push({
        tool_call_id: toolCall.id,
        output: JSON.stringify(weather),
      });
    }
  }

  // Submit tool outputs
  run = await openai.beta.threads.runs.submitToolOutputs(thread.id, run.id, {
    tool_outputs: toolOutputs,
  });

  // Continue polling...
}
```

---

## Vector Stores

Vector stores enable efficient semantic search over large document collections.

### Create a Vector Store

```typescript
const vectorStore = await openai.beta.vectorStores.create({
  name: "Legal Documents",
  metadata: {
    department: "legal",
    category: "contracts",
  },
  expires_after: {
    anchor: "last_active_at",
    days: 7, // Auto-delete 7 days after last use
  },
});
```

### Add Files to Vector Store

**Single File:**

```typescript
const file = await openai.files.create({
  file: fs.createReadStream("contract.pdf"),
  purpose: "assistants",
});

await openai.beta.vectorStores.files.create(vectorStore.id, {
  file_id: file.id,
});
```

**Batch Upload:**

```typescript
const fileBatch = await openai.beta.vectorStores.fileBatches.create(vectorStore.id, {
  file_ids: ["file_abc123", "file_def456", "file_ghi789"],
});

// Poll for batch completion
let batch = await openai.beta.vectorStores.fileBatches.retrieve(vectorStore.id, fileBatch.id);
while (batch.status === 'in_progress') {
  await new Promise(resolve => setTimeout(resolve, 1000));
  batch = await openai.beta.vectorStores.fileBatches.retrieve(vectorStore.id, fileBatch.id);
}
```

### Check Vector Store Status

```typescript
const vectorStore = await openai.beta.vectorStores.retrieve("vs_abc123");

console.log(vectorStore.status); // "in_progress", "completed", "failed"
console.log(vectorStore.file_counts); // { in_progress: 0, completed: 50, failed: 0 }
```

**⚠️ Important**: Wait for `status: "completed"` before using with file search.

### List Vector Stores

```typescript
const stores = await openai.beta.vectorStores.list({
  limit: 20,
  order: "desc",
});
```

### Update Vector Store

```typescript
const vectorStore = await openai.beta.vectorStores.update("vs_abc123", {
  name: "Updated Name",
  metadata: { updated: "true" },
});
```

### Delete Vector Store

```typescript
await openai.beta.vectorStores.del("vs_abc123");
```

---

## File Uploads

Upload files for use with Code Interpreter or File Search.

### Upload a File

```typescript
import fs from 'fs';

const file = await openai.files.create({
  file: fs.createReadStream("document.pdf"),
  purpose: "assistants",
});

console.log(file.id); // file_abc123
```

**Supported Formats:**
- **Code Interpreter**: .c, .cpp, .csv, .docx, .html, .java, .json, .md, .pdf, .php, .pptx, .py, .rb, .tex, .txt, .css, .jpeg, .jpg, .js, .gif, .png, .tar, .ts, .xlsx, .xml, .zip
- **File Search**: .c, .cpp, .docx, .html, .java, .json, .md, .pdf, .php, .pptx, .py, .rb, .tex, .txt, .css, .js, .ts, .go

**Size Limits:**
- Code Interpreter: 512 MB per file
- File Search: 512 MB per file
- Vector Store: Up to 10,000 files

### Retrieve File Info

```typescript
const file = await openai.files.retrieve("file_abc123");
```

### Download File Content

```typescript
const content = await openai.files.content("file_abc123");
// Returns binary content
```

### Delete a File

```typescript
await openai.files.del("file_abc123");
```

### List Files

```typescript
const files = await openai.files.list({
  purpose: "assistants",
});
```

---

## Thread Lifecycle Management

Proper thread lifecycle management prevents common errors.

### Pattern 1: One Thread Per User

```typescript
async function getOrCreateUserThread(userId: string): Promise<string> {
  // Check if thread exists in your database
  let threadId = await db.getThreadIdForUser(userId);

  if (!threadId) {
    // Create new thread
    const thread = await openai.beta.threads.create({
      metadata: { user_id: userId },
    });
    threadId = thread.id;
    await db.saveThreadIdForUser(userId, threadId);
  }

  return threadId;
}
```

### Pattern 2: Active Run Check

```typescript
async function ensureNoActiveRun(threadId: string) {
  const runs = await openai.beta.threads.runs.list(threadId, {
    limit: 1,
    order: "desc",
  });

  const latestRun = runs.data[0];
  if (latestRun && ['queued', 'in_progress', 'cancelling'].includes(latestRun.status)) {
    throw new Error('Thread already has an active run. Wait or cancel first.');
  }
}

// Before creating new run
await ensureNoActiveRun(threadId);
const run = await openai.beta.threads.runs.create(threadId, { assistant_id });
```

### Pattern 3: Thread Cleanup

```typescript
async function cleanupOldThreads(maxAgeHours = 24) {
  const threads = await openai.beta.threads.list({ limit: 100 });

  for (const thread of threads.data) {
    const createdAt = new Date(thread.created_at * 1000);
    const ageHours = (Date.now() - createdAt.getTime()) / (1000 * 60 * 60);

    if (ageHours > maxAgeHours) {
      await openai.beta.threads.del(thread.id);
    }
  }
}
```

---

## Error Handling

### Common Errors and Solutions

**1. Thread Already Has Active Run**

```
Error: 400 Can't add messages to thread_xxx while a run run_xxx is active.
```

**Solution:**
```typescript
// Wait for run to complete or cancel it
const run = await openai.beta.threads.runs.retrieve(threadId, runId);
if (['queued', 'in_progress'].includes(run.status)) {
  await openai.beta.threads.runs.cancel(threadId, runId);
  // Wait for cancellation
  while (run.status !== 'cancelled') {
    await new Promise(resolve => setTimeout(resolve, 500));
    run = await openai.beta.threads.runs.retrieve(threadId, runId);
  }
}
```

**2. Run Polling Timeout**

Long-running tasks may exceed reasonable polling windows.

**Solution:**
```typescript
async function pollWithTimeout(threadId: string, runId: string, maxSeconds = 300) {
  const startTime = Date.now();

  while (true) {
    const run = await openai.beta.threads.runs.retrieve(threadId, runId);

    if (!['queued', 'in_progress'].includes(run.status)) {
      return run;
    }

    const elapsed = (Date.now() - startTime) / 1000;
    if (elapsed > maxSeconds) {
      await openai.beta.threads.runs.cancel(threadId, runId);
      throw new Error('Run exceeded timeout');
    }

    await new Promise(resolve => setTimeout(resolve, 1000));
  }
}
```

**3. Vector Store Not Ready**

Using vector store before indexing completes.

**Solution:**
```typescript
async function waitForVectorStore(vectorStoreId: string) {
  let store = await openai.beta.vectorStores.retrieve(vectorStoreId);

  while (store.status === 'in_progress') {
    await new Promise(resolve => setTimeout(resolve, 2000));
    store = await openai.beta.vectorStores.retrieve(vectorStoreId);
  }

  if (store.status === 'failed') {
    throw new Error('Vector store indexing failed');
  }

  return store;
}
```

**4. File Upload Format Issues**

Unsupported file formats cause errors.

**Solution:**
```typescript
const SUPPORTED_FORMATS = {
  code_interpreter: ['.csv', '.json', '.pdf', '.txt', '.py', '.js', '.xlsx'],
  file_search: ['.pdf', '.docx', '.txt', '.md', '.html'],
};

function validateFile(filename: string, tool: string) {
  const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
  if (!SUPPORTED_FORMATS[tool].includes(ext)) {
    throw new Error(`Unsupported file format for ${tool}: ${ext}`);
  }
}
```

See `references/top-errors.md` for complete error catalog.

---

## Production Best Practices

### 1. Use Assistant IDs (Don't Recreate)

**❌ Bad:**
```typescript
// Creates new assistant on every request!
const assistant = await openai.beta.assistants.create({ ... });
```

**✅ Good:**
```typescript
// Create once, store ID, reuse
const ASSISTANT_ID = process.env.ASSISTANT_ID || await createAssistant();

async function createAssistant() {
  const assistant = await openai.beta.assistants.create({ ... });
  console.log('Save this ID:', assistant.id);
  return assistant.id;
}
```

### 2. Implement Proper Error Handling

```typescript
async function createRunWithRetry(threadId: string, assistantId: string, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await openai.beta.threads.runs.create(threadId, {
        assistant_id: assistantId,
      });
    } catch (error) {
      if (error.status === 429) {
        // Rate limit - wait and retry
        await new Promise(resolve => setTimeout(resolve, 2000 * (i + 1)));
        continue;
      }

      if (error.message?.includes('active run')) {
        // Wait for active run to complete
        await new Promise(resolve => setTimeout(resolve, 5000));
        continue;
      }

      throw error; // Other errors
    }
  }

  throw new Error('Max retries exceeded');
}
```

### 3. Monitor Costs

```typescript
// Track usage
const run = await openai.beta.threads.runs.retrieve(threadId, runId);
console.log('Tokens used:', run.usage);
// { prompt_tokens: 150, completion_tokens: 200, total_tokens: 350 }

// Set limits
const run = await openai.beta.threads.runs.create(threadId, {
  assistant_id: assistantId,
  max_prompt_tokens: 1000,
  max_completion_tokens: 500,
});
```

### 4. Clean Up Resources

```typescript
// Delete old threads
async function cleanupUserThread(userId: string) {
  const threadId = await db.getThreadIdForUser(userId);
  if (threadId) {
    await openai.beta.threads.del(threadId);
    await db.deleteThreadIdForUser(userId);
  }
}

// Delete unused vector stores
async function cleanupVectorStores(keepDays = 30) {
  const stores = await openai.beta.vectorStores.list({ limit: 100 });

  for (const store of stores.data) {
    const ageSeconds = Date.now() / 1000 - store.created_at;
    const ageDays = ageSeconds / (60 * 60 * 24);

    if (ageDays > keepDays) {
      await openai.beta.vectorStores.del(store.id);
    }
  }
}
```

### 5. Use Streaming for Better UX

```typescript
// Show progress in real-time
async function streamToUser(threadId: string, assistantId: string) {
  const stream = await openai.beta.threads.runs.stream(threadId, {
    assistant_id: assistantId,
  });

  for await (const event of stream) {
    if (event.event === 'thread.message.delta') {
      const delta = event.data.delta.content?.[0]?.text?.value;
      if (delta) {
        // Send to user immediately
        sendToClient(delta);
      }
    }
  }
}
```

---

## Relationship to Other Skills

### vs. openai-api Skill

**openai-api** (Chat Completions):
- Stateless requests
- Manual history management
- Direct responses
- Use for: Simple text generation, function calling

**openai-assistants**:
- Stateful conversations (threads)
- Automatic history management
- Built-in tools (Code Interpreter, File Search)
- Use for: Chatbots, data analysis, RAG

### vs. openai-responses Skill

**openai-responses** (Responses API):
- ✅ **Recommended for new projects**
- Better reasoning preservation
- Modern MCP integration
- Active development

**openai-assistants**:
- ⚠️ **Deprecated in H1 2026**
- Use for legacy apps
- Migration path available

**Migration:** See `references/migration-to-responses.md`

---

## Migration from v1 to v2

**v1 deprecated**: December 18, 2024

**Key Changes:**
1. **Retrieval → File Search**: `retrieval` tool replaced with `file_search`
2. **Vector Stores**: Files now organized in vector stores (10,000 file limit)
3. **Instructions Limit**: Increased from 32k to 256k characters
4. **File Attachments**: Now message-level instead of assistant-level

See `references/migration-from-v1.md` for complete guide.

---

## Next Steps

**Templates:**
- `templates/basic-assistant.ts` - Simple math tutor
- `templates/code-interpreter-assistant.ts` - Data analysis
- `templates/file-search-assistant.ts` - RAG with vector stores
- `templates/function-calling-assistant.ts` - Custom tools
- `templates/streaming-assistant.ts` - Real-time streaming

**References:**
- `references/top-errors.md` - 12 common errors and solutions
- `references/thread-lifecycle.md` - Thread management patterns
- `references/vector-stores.md` - Vector store deep dive

**Related Skills:**
- `openai-responses` - Modern replacement (recommended)
- `openai-api` - Chat Completions (stateless)

---

**Last Updated**: 2025-10-25
**Package Version**: openai@6.7.0
**Status**: Production Ready (Deprecated H1 2026)
