openai-assistants

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.

31 stars

Best use case

openai-assistants is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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.

Teams using openai-assistants should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/openai-assistants/SKILL.md --create-dirs "https://raw.githubusercontent.com/ovachiever/droid-tings/main/skills/openai-assistants/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/openai-assistants/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How openai-assistants Compares

Feature / Agentopenai-assistantsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# 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)

Related Skills

openai-responses

31
from ovachiever/droid-tings

Build agentic AI applications with OpenAI's Responses API - the stateful successor to Chat Completions. Preserves reasoning across turns for 5% better multi-turn performance and 40-80% improved cache utilization. Use when: building AI agents with persistent reasoning, integrating MCP servers for external tools, using built-in Code Interpreter/File Search/Web Search, managing stateful conversations, implementing background processing for long tasks, or migrating from Chat Completions to gain polymorphic outputs and server-side tools.

OpenAI Apps MCP

31
from ovachiever/droid-tings

Build ChatGPT apps with MCP servers on Cloudflare Workers. Extend ChatGPT with custom tools and interactive widgets (HTML/JS UI). Use when: developing ChatGPT extensions, implementing MCP servers, or troubleshooting CORS blocking (allow chatgpt.com), widget 404s (missing ui://widget/), wrong MIME type (text/html+skybridge), or ASSETS binding undefined.

openai-api

31
from ovachiever/droid-tings

Build with OpenAI's stateless APIs - Chat Completions (GPT-5, GPT-4o), Embeddings, Images (DALL-E 3), Audio (Whisper + TTS), and Moderation. Includes Node.js SDK and fetch-based approaches for Cloudflare Workers. Use when: implementing chat completions with GPT-5/GPT-4o, streaming responses with SSE, using function calling/tools, creating structured outputs with JSON schemas, generating embeddings for RAG (text-embedding-3-small/large), generating images with DALL-E 3, editing images with GPT-Image-1, transcribing audio with Whisper, synthesizing speech with TTS (11 voices), moderating content (11 safety categories), or troubleshooting rate limits (429), invalid API keys (401), function calling failures, streaming parse errors, embeddings dimension mismatches, or token limit exceeded.

openai-agents

31
from ovachiever/droid-tings

Build AI applications with OpenAI Agents SDK - text agents, voice agents (realtime), multi-agent workflows with handoffs, tools with Zod schemas, input/output guardrails, structured outputs, and streaming. Deploy to Cloudflare Workers, Next.js, or React with human-in-the-loop patterns. Use when: building text-based agents with tools and Zod schemas, creating realtime voice agents with WebRTC/WebSocket, implementing multi-agent workflows with handoffs between specialists, setting up input/output guardrails for safety, requiring human approval for critical actions, streaming agent responses, deploying agents to Cloudflare Workers or Next.js, or troubleshooting Zod schema type errors, MCP tracing failures, infinite loops (MaxTurnsExceededError), tool call failures, schema mismatches, or voice agent handoff constraints.

zustand-state-management

31
from ovachiever/droid-tings

Build type-safe global state in React applications with Zustand. Supports TypeScript, persist middleware, devtools, slices pattern, and Next.js SSR. Use when setting up React state, migrating from Redux/Context API, implementing localStorage persistence, or troubleshooting Next.js hydration errors, TypeScript inference issues, or infinite render loops.

zinc-database

31
from ovachiever/droid-tings

Access ZINC (230M+ purchasable compounds). Search by ZINC ID/SMILES, similarity searches, 3D-ready structures for docking, analog discovery, for virtual screening and drug discovery.

zarr-python

31
from ovachiever/droid-tings

Chunked N-D arrays for cloud storage. Compressed arrays, parallel I/O, S3/GCS integration, NumPy/Dask/Xarray compatible, for large-scale scientific computing pipelines.

youtube-transcript

31
from ovachiever/droid-tings

Download YouTube video transcripts when user provides a YouTube URL or asks to download/get/fetch a transcript from YouTube. Also use when user wants to transcribe or get captions/subtitles from a YouTube video.

xlsx

31
from ovachiever/droid-tings

Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas

wordpress-plugin-core

31
from ovachiever/droid-tings

Build secure WordPress plugins with core patterns for hooks, database interactions, Settings API, custom post types, REST API, and AJAX. Covers three architecture patterns (Simple, OOP, PSR-4) and the Security Trinity. Use when creating plugins, implementing nonces/sanitization/escaping, working with $wpdb prepared statements, or troubleshooting SQL injection, XSS, CSRF vulnerabilities, or plugin activation errors.

whisper

31
from ovachiever/droid-tings

OpenAI's general-purpose speech recognition model. Supports 99 languages, transcription, translation to English, and language identification. Six model sizes from tiny (39M params) to large (1550M params). Use for speech-to-text, podcast transcription, or multilingual audio processing. Best for robust, multilingual ASR.

weights-and-biases

31
from ovachiever/droid-tings

Track ML experiments with automatic logging, visualize training in real-time, optimize hyperparameters with sweeps, and manage model registry with W&B - collaborative MLOps platform