Back to skills
SkillHub ClubAnalyze Data & AIFull StackBackendData / AI

openai-assistants

Build stateful chatbots with OpenAI Assistants API v2 - Code Interpreter, File Search (10k files), Function Calling. Prevents 10 documented errors including vector store upload bugs, temperature parameter conflicts, memory leaks. Deprecated (sunset August 2026); use openai-responses for new projects. Use when: maintaining legacy chatbots, implementing RAG with vector stores, or troubleshooting thread errors, vector store delays, uploadAndPoll issues.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
624
Hot score
99
Updated
March 20, 2026
Overall rating
A8.0
Composite score
6.6
Best-practice grade
B75.6

Install command

npx @skill-hub/cli install jezweb-claude-skills-openai-assistants

Repository

jezweb/claude-skills

Skill path: skills/openai-assistants

Build stateful chatbots with OpenAI Assistants API v2 - Code Interpreter, File Search (10k files), Function Calling. Prevents 10 documented errors including vector store upload bugs, temperature parameter conflicts, memory leaks. Deprecated (sunset August 2026); use openai-responses for new projects. Use when: maintaining legacy chatbots, implementing RAG with vector stores, or troubleshooting thread errors, vector store delays, uploadAndPoll issues.

Open repository

Best for

Primary workflow: Analyze Data & AI.

Technical facets: Full Stack, Backend, Data / AI.

Target audience: Developers maintaining legacy chatbots built on OpenAI Assistants API who need to migrate to v2 before the August 2026 sunset.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: jezweb.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install openai-assistants into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/jezweb/claude-skills before adding openai-assistants to shared team environments
  • Use openai-assistants for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: openai-assistants
description: |
  Build stateful chatbots with OpenAI Assistants API v2 - Code Interpreter, File Search (10k files), Function Calling. Prevents 10 documented errors including vector store upload bugs, temperature parameter conflicts, memory leaks. Deprecated (sunset August 2026); use openai-responses for new projects.

  Use when: maintaining legacy chatbots, implementing RAG with vector stores, or troubleshooting thread errors, vector store delays, uploadAndPoll issues.
user-invocable: true
---

# OpenAI Assistants API v2

**Status**: Production Ready (⚠️ Deprecated - Sunset August 26, 2026)
**Package**: [email protected]
**Last Updated**: 2026-01-21
**v1 Deprecated**: December 18, 2024
**v2 Sunset**: August 26, 2026 (migrate to Responses API)

---

## ⚠️ Deprecation Notice

**OpenAI is deprecating Assistants API in favor of [Responses API](../openai-responses/SKILL.md).**

**Timeline**: v1 deprecated Dec 18, 2024 | v2 sunset August 26, 2026

**Use this skill if**: Maintaining legacy apps or migrating existing code (12-18 month window)
**Don't use if**: Starting new projects (use `openai-responses` skill instead)

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

---

## Quick Start

```bash
npm install [email protected]
```

```typescript
import OpenAI from 'openai';

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

// 1. Create assistant
const assistant = await openai.beta.assistants.create({
  name: "Math Tutor",
  instructions: "You are a math tutor. Use code interpreter for calculations.",
  tools: [{ type: "code_interpreter" }],
  model: "gpt-5",
});

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

// 3. Add message
await openai.beta.threads.messages.create(thread.id, {
  role: "user",
  content: "Solve: 3x + 11 = 14",
});

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

// 5. Poll for completion
let status = await openai.beta.threads.runs.retrieve(thread.id, run.id);
while (status.status !== 'completed') {
  await new Promise(r => setTimeout(r, 1000));
  status = await openai.beta.threads.runs.retrieve(thread.id, run.id);
}

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

---

## Core Concepts

**Four Main Objects:**

1. **Assistants**: Configured AI with instructions (max 256k chars in v2, was 32k in v1), model, tools, metadata
2. **Threads**: Conversation containers with persistent message history (max 100k messages)
3. **Messages**: User/assistant messages with optional file attachments
4. **Runs**: Async execution with states (queued, in_progress, requires_action, completed, failed, expired)

---

## Key API Patterns

### Assistants

```typescript
const assistant = await openai.beta.assistants.create({
  model: "gpt-5",
  instructions: "System prompt (max 256k chars in v2)",
  tools: [{ type: "code_interpreter" }, { type: "file_search" }],
  tool_resources: { file_search: { vector_store_ids: ["vs_123"] } },
});
```

**Key Limits**: 256k instruction chars (v2), 128 tools max, 16 metadata pairs

### Threads & Messages

```typescript
// Create thread with messages
const thread = await openai.beta.threads.create({
  messages: [{ role: "user", content: "Hello" }],
});

// Add message with attachments
await openai.beta.threads.messages.create(thread.id, {
  role: "user",
  content: "Analyze this",
  attachments: [{ file_id: "file_123", tools: [{ type: "code_interpreter" }] }],
});

// List messages
const msgs = await openai.beta.threads.messages.list(thread.id);
```

**Key Limits**: 100k messages per thread

---

### Runs

```typescript
// Create run with optional overrides
const run = await openai.beta.threads.runs.create(thread.id, {
  assistant_id: "asst_123",
  additional_messages: [{ role: "user", content: "Question" }],
  max_prompt_tokens: 1000,
  max_completion_tokens: 500,
});

// Poll until complete
let status = await openai.beta.threads.runs.retrieve(thread.id, run.id);
while (['queued', 'in_progress'].includes(status.status)) {
  await new Promise(r => setTimeout(r, 1000));
  status = await openai.beta.threads.runs.retrieve(thread.id, run.id);
}
```

**Run States**: `queued` → `in_progress` → `requires_action` (function calling) / `completed` / `failed` / `cancelled` / `expired` (10 min max)

---

### Streaming

```typescript
const stream = await openai.beta.threads.runs.stream(thread.id, { assistant_id });

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

**Key Events**: `thread.run.created`, `thread.message.delta` (streaming content), `thread.run.step.delta` (tool progress), `thread.run.completed`, `thread.run.requires_action` (function calling)

---

## Tools

### Code Interpreter

Runs Python code in sandbox. Generates charts, processes files (CSV, JSON, PDF, images). Max 512MB per file.

```typescript
// Attach file to message
attachments: [{ file_id: "file_123", tools: [{ type: "code_interpreter" }] }]

// Access generated files
for (const content of message.content) {
  if (content.type === 'image_file') {
    const fileContent = await openai.files.content(content.image_file.file_id);
  }
}
```

### File Search (RAG)

Semantic search with vector stores. **10,000 files max** (v2, was 20 in v1). **Pricing**: $0.10/GB/day (1GB free).

```typescript
// Create vector store
const vs = await openai.beta.vectorStores.create({ name: "Docs" });
await openai.beta.vectorStores.files.create(vs.id, { file_id: "file_123" });

// Wait for indexing
let store = await openai.beta.vectorStores.retrieve(vs.id);
while (store.status === 'in_progress') {
  await new Promise(r => setTimeout(r, 2000));
  store = await openai.beta.vectorStores.retrieve(vs.id);
}

// Use in assistant
tool_resources: { file_search: { vector_store_ids: [vs.id] } }
```

**⚠️ Wait for `status: 'completed'` before using**

### Function Calling

Submit tool outputs when run.status === 'requires_action':

```typescript
if (run.status === 'requires_action') {
  const toolCalls = run.required_action.submit_tool_outputs.tool_calls;
  const outputs = toolCalls.map(tc => ({
    tool_call_id: tc.id,
    output: JSON.stringify(yourFunction(JSON.parse(tc.function.arguments))),
  }));

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

## File 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 (512MB max)

**File Search**: .c, .cpp, .docx, .html, .java, .json, .md, .pdf, .php, .pptx, .py, .rb, .tex, .txt, .css, .js, .ts, .go (512MB max)

---

## Known Issues

**1. Thread Already Has Active Run**
```
Error: 400 Can't add messages to thread_xxx while a run run_xxx is active.
```
**Fix**: Cancel active run first: `await openai.beta.threads.runs.cancel(threadId, runId)`

**2. Run Polling Timeout / Incomplete Status**
```
Error: OpenAIError: Final run has not been received
```
**Why It Happens**: Long-running tasks may exceed polling windows or finish with `incomplete` status
**Prevention**: Handle incomplete runs gracefully
```typescript
try {
  const stream = await openai.beta.threads.runs.stream(thread.id, { assistant_id });
  for await (const event of stream) {
    if (event.event === 'thread.message.delta') {
      process.stdout.write(event.data.delta.content?.[0]?.text?.value || '');
    }
  }
} catch (error) {
  if (error.message?.includes('Final run has not been received')) {
    // Run ended with 'incomplete' status - thread can continue
    const run = await openai.beta.threads.runs.retrieve(thread.id, runId);
    if (run.status === 'incomplete') {
      // Handle: prompt user to continue, reduce max_completion_tokens, etc.
    }
  }
}
```
**Source**: [GitHub Issues #945](https://github.com/openai/openai-node/issues/945), [#1306](https://github.com/openai/openai-node/issues/1306), [#1439](https://github.com/openai/openai-node/issues/1439)

**3. Vector Store Not Ready**
Using vector store before indexing completes.
**Fix**: Poll `vectorStores.retrieve()` until `status === 'completed'` (see File Search section)

**4. File Upload Format Issues**
Unsupported file formats cause silent failures.
**Fix**: Validate file extensions before upload (see File Formats section)

**5. Vector Store Upload Documentation Incorrect**
```
Error: No 'files' provided to process
```
**Why It Happens**: Official documentation shows incorrect usage of `uploadAndPoll`
**Prevention**: Wrap file streams in `{ files: [...] }` object
```typescript
// ✅ Correct
await openai.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
  files: fileStreams
});

// ❌ Wrong (shown in official docs)
await openai.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, fileStreams);
```
**Source**: [GitHub Issue #1337](https://github.com/openai/openai-node/issues/1337)

**6. Reasoning Models Reject Temperature Parameter**
```
Error: Unsupported parameter: 'temperature' is not supported with this model
```
**Why It Happens**: When updating assistant to o3-mini/o1-preview/o1-mini, old temperature settings persist
**Prevention**: Explicitly set temperature to `null`
```typescript
await openai.beta.assistants.update(assistantId, {
  model: 'o3-mini',
  reasoning_effort: 'medium',
  temperature: null,  // ✅ Must explicitly clear
  top_p: null
});
```
**Source**: [GitHub Issue #1318](https://github.com/openai/openai-node/issues/1318)

**7. uploadAndPoll Returns Vector Store ID Instead of Batch ID**
```
Error: Invalid 'batch_id': 'vs_...'. Expected an ID that begins with 'vsfb_'.
```
**Why It Happens**: `uploadAndPoll` returns vector store object instead of batch object
**Prevention**: Use alternative methods to get batch ID
```typescript
// Option 1: Use createAndPoll after separate upload
const batch = await openai.vectorStores.fileBatches.createAndPoll(
  vectorStoreId,
  { file_ids: uploadedFileIds }
);

// Option 2: List batches to find correct ID
const batches = await openai.vectorStores.fileBatches.list(vectorStoreId);
const batchId = batches.data[0].id; // starts with 'vsfb_'
```
**Source**: [GitHub Issue #1700](https://github.com/openai/openai-node/issues/1700)

**8. Vector Store File Delete Affects All Stores**
**Warning**: Deleting a file from one vector store removes it from ALL vector stores
```typescript
// ❌ This deletes file from VS_A, VS_B, AND VS_C
await openai.vectorStores.files.delete('VS_A', 'file-xxx');
```
**Why It Happens**: SDK or API bug - delete operation has global effect
**Prevention**: Avoid sharing files across multiple vector stores if selective deletion is needed
**Source**: [GitHub Issue #1710](https://github.com/openai/openai-node/issues/1710)

**9. Memory Leak in Large File Uploads (Community-sourced)**
**Source**: [GitHub Issue #1052](https://github.com/openai/openai-node/issues/1052) | **Status**: OPEN
**Impact**: ~44MB leaked per 22MB file upload in long-running servers
**Why It Happens**: When uploading large files from streams (S3, etc.) using `vectorStores.fileBatches.uploadAndPoll`, memory may not be released after upload completes
**Verified**: Maintainer acknowledged, reduced in v4.58.1 but not eliminated
**Workaround**: Monitor memory usage in long-lived servers; restart periodically or use separate worker processes

**10. Thread Already Has Active Run - Race Condition (Community-sourced)**
**Enhancement to Issue #1**: When canceling an active run, race conditions may occur if the run completes before cancellation
```typescript
async function createRunSafely(threadId: string, assistantId: string) {
  // Check for active runs first
  const runs = await openai.beta.threads.runs.list(threadId, { limit: 1 });
  const activeRun = runs.data.find(r =>
    ['queued', 'in_progress', 'requires_action'].includes(r.status)
  );

  if (activeRun) {
    try {
      await openai.beta.threads.runs.cancel(threadId, activeRun.id);

      // Wait for cancellation to complete
      let run = await openai.beta.threads.runs.retrieve(threadId, activeRun.id);
      while (run.status === 'cancelling') {
        await new Promise(r => setTimeout(r, 500));
        run = await openai.beta.threads.runs.retrieve(threadId, activeRun.id);
      }
    } catch (error) {
      // Ignore "already completed" errors - run finished naturally
      if (!error.message?.includes('completed')) throw error;
    }
  }

  return openai.beta.threads.runs.create(threadId, { assistant_id: assistantId });
}
```
**Source**: [OpenAI Community Forum](https://community.openai.com/t/error-running-thread-already-has-an-active-run/782118)

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

## Relationship to Other Skills

**openai-api** (Chat Completions): Stateless, manual history, direct responses. Use for simple generation.

**openai-responses** (Responses API): ✅ **Recommended for new projects**. Better reasoning, modern MCP integration, active development.

**openai-assistants**: ⚠️ **Deprecated H1 2026**. Use for legacy apps only. Migration: `references/migration-to-responses.md`

---

## v1 to v2 Migration

**v1 deprecated**: Dec 18, 2024

**Key Changes**: `retrieval` → `file_search`, vector stores (10k files vs 20), 256k instructions (vs 32k), message-level file attachments

See `references/migration-from-v1.md`

---

**Templates**: `templates/basic-assistant.ts`, `code-interpreter-assistant.ts`, `file-search-assistant.ts`, `function-calling-assistant.ts`, `streaming-assistant.ts`

**References**: `references/top-errors.md`, `thread-lifecycle.md`, `vector-stores.md`, `migration-to-responses.md`, `migration-from-v1.md`

**Related Skills**: `openai-responses` (recommended), `openai-api`

---

**Last Updated**: 2026-01-21
**Package**: [email protected]
**Status**: Production Ready (⚠️ Deprecated - Sunset August 26, 2026)
**Changes**: Added 6 new known issues (vector store upload bugs, o3-mini temperature, memory leak), enhanced streaming error handling


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### ../openai-responses/SKILL.md

```markdown
---
name: openai-responses
description: |
  Build agentic AI with OpenAI Responses API - stateful conversations with preserved reasoning, built-in tools (Code Interpreter, File Search, Web Search), and MCP integration. Prevents 11 documented errors.

  Use when: building agents with persistent reasoning, using server-side tools, or migrating from Chat Completions/Assistants for better multi-turn performance.
user-invocable: true
---

# OpenAI Responses API

**Status**: Production Ready
**Last Updated**: 2026-01-21
**API Launch**: March 2025
**Dependencies**: [email protected] (Node.js) or fetch API (Cloudflare Workers)

---

## What Is the Responses API?

OpenAI's unified interface for agentic applications, launched **March 2025**. Provides **stateful conversations** with **preserved reasoning state** across turns.

**Key Innovation:** Unlike Chat Completions (reasoning discarded between turns), Responses **preserves the model's reasoning notebook**, improving performance by **5% on TAUBench** and enabling better multi-turn interactions.

**vs Chat Completions:**

| Feature | Chat Completions | Responses API |
|---------|-----------------|---------------|
| State | Manual history tracking | Automatic (conversation IDs) |
| Reasoning | Dropped between turns | Preserved across turns (+5% TAUBench) |
| Tools | Client-side round trips | Server-side hosted |
| Output | Single message | Polymorphic (8 types) |
| Cache | Baseline | **40-80% better utilization** |
| MCP | Manual | Built-in |

---

## Quick Start

```bash
npm install [email protected]
```

```typescript
import OpenAI from 'openai';

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

const response = await openai.responses.create({
  model: 'gpt-5',
  input: 'What are the 5 Ds of dodgeball?',
});

console.log(response.output_text);
```

**Key differences from Chat Completions:**
- Endpoint: `/v1/responses` (not `/v1/chat/completions`)
- Parameter: `input` (not `messages`)
- Role: `developer` (not `system`)
- Output: `response.output_text` (not `choices[0].message.content`)

---

## When to Use Responses vs Chat Completions

**Use Responses:**
- Agentic applications (reasoning + actions)
- Multi-turn conversations (preserved reasoning = +5% TAUBench)
- Built-in tools (Code Interpreter, File Search, Web Search, MCP)
- Background processing (60s standard, 10min extended timeout)

**Use Chat Completions:**
- Simple one-off generation
- Fully stateless interactions
- Legacy integrations

---

## Stateful Conversations

**Automatic State Management** using conversation IDs:

```typescript
// Create conversation
const conv = await openai.conversations.create({
  metadata: { user_id: 'user_123' },
});

// First turn
const response1 = await openai.responses.create({
  model: 'gpt-5',
  conversation: conv.id,
  input: 'What are the 5 Ds of dodgeball?',
});

// Second turn - model remembers context + reasoning
const response2 = await openai.responses.create({
  model: 'gpt-5',
  conversation: conv.id,
  input: 'Tell me more about the first one',
});
```

**Benefits:** No manual history tracking, reasoning preserved, 40-80% better cache utilization

**Conversation Limits:** 90-day expiration

---

## Built-in Tools (Server-Side)

**Server-side hosted tools** eliminate backend round trips:

| Tool | Purpose | Notes |
|------|---------|-------|
| `code_interpreter` | Execute Python code | Sandboxed, 30s timeout (use `background: true` for longer) |
| `file_search` | RAG without vector stores | Max 512MB per file, supports PDF/Word/Markdown/HTML/code |
| `web_search` | Real-time web information | Automatic source citations |
| `image_generation` | DALL-E integration | DALL-E 3 default |
| `mcp` | Connect external tools | OAuth supported, tokens NOT stored |

**Usage:**
```typescript
const response = await openai.responses.create({
  model: 'gpt-5',
  input: 'Calculate mean of: 10, 20, 30, 40, 50',
  tools: [{ type: 'code_interpreter' }],
});
```

### Web Search TypeScript Note

**TypeScript Limitation**: The `web_search` tool's `external_web_access` option is missing from SDK types (as of v6.16.0).

**Workaround**:
```typescript
const response = await openai.responses.create({
  model: 'gpt-5',
  input: 'Search for recent news',
  tools: [{
    type: 'web_search',
    external_web_access: true,
  } as any],  // ✅ Type assertion to suppress error
});
```

**Source**: [GitHub Issue #1716](https://github.com/openai/openai-node/issues/1716)

---

## MCP Server Integration

Built-in support for **Model Context Protocol (MCP)** servers to connect external tools (Stripe, databases, custom APIs).

### User Approval Requirement

**By default, explicit user approval is required** before any data is shared with a remote MCP server (security feature).

**Handling Approval**:
```typescript
const response = await openai.responses.create({
  model: 'gpt-5',
  input: 'Get my Stripe balance',
  tools: [{
    type: 'mcp',
    server_label: 'stripe',
    server_url: 'https://mcp.stripe.com',
    authorization: process.env.STRIPE_TOKEN,
  }],
});

if (response.status === 'requires_approval') {
  // Show user: "This action requires sharing data with Stripe. Approve?"
  // After user approves, retry with approval token
}
```

**Alternative**: Pre-approve MCP servers in OpenAI dashboard (users configure trusted servers via settings)

**Source**: [Official MCP Guide](https://platform.openai.com/docs/guides/tools-connectors-mcp)

### Basic MCP Usage

```typescript
const response = await openai.responses.create({
  model: 'gpt-5',
  input: 'Roll 2d6 dice',
  tools: [{
    type: 'mcp',
    server_label: 'dice',
    server_url: 'https://example.com/mcp',
    authorization: process.env.TOKEN, // ⚠️ NOT stored, required each request
  }],
});
```

**MCP Output Types:**
- `mcp_list_tools` - Tools discovered on server
- `mcp_call` - Tool invocation + result
- `message` - Final response

---

## Reasoning Preservation

**Key Innovation:** Model's internal reasoning state survives across turns (unlike Chat Completions which discards it).

**Visual Analogy:**
- Chat Completions: Model tears out scratchpad page before responding
- Responses API: Scratchpad stays open for next turn

**Performance:** +5% on TAUBench (GPT-5) purely from preserved reasoning

**Reasoning Summaries** (free):
```typescript
response.output.forEach(item => {
  if (item.type === 'reasoning') console.log(item.summary[0].text);
  if (item.type === 'message') console.log(item.content[0].text);
});
```

### Important: Reasoning Traces Privacy

**What You Get**: Reasoning summaries (not full internal traces)
**What OpenAI Keeps**: Full chain-of-thought reasoning (proprietary, for security/privacy)

For GPT-5-Thinking models:
- OpenAI preserves reasoning **internally** in their backend
- This preserved reasoning improves multi-turn performance (+5% TAUBench)
- But developers only receive **summaries**, not the actual chain-of-thought
- Full reasoning traces are not exposed (OpenAI's IP protection)

**Source**: [Sean Goedecke Analysis](https://www.seangoedecke.com/responses-api/)

---

## Background Mode

For long-running tasks, use `background: true`:

```typescript
const response = await openai.responses.create({
  model: 'gpt-5',
  input: 'Analyze 500-page document',
  background: true,
  tools: [{ type: 'file_search', file_ids: [fileId] }],
});

// Poll for completion (check every 5s)
const result = await openai.responses.retrieve(response.id);
if (result.status === 'completed') console.log(result.output_text);
```

**Timeout Limits:**
- Standard: 60 seconds
- Background: 10 minutes

### Performance Considerations

**Time-to-First-Token (TTFT) Latency:**
Background mode currently has higher TTFT compared to synchronous responses. OpenAI is working to reduce this gap.

**Recommendation:**
- For user-facing real-time responses, use sync mode (lower latency)
- For long-running async tasks, use background mode (latency acceptable)

**Source**: [OpenAI Background Mode Docs](https://platform.openai.com/docs/guides/background)

---

## Data Retention and Privacy

**Default Retention**: 30 days when `store: true` (default)
**Zero Data Retention (ZDR)**: Organizations with ZDR automatically enforce `store: false`
**Background Mode**: NOT ZDR compatible (stores data ~10 minutes for polling)

**Timeline**:
- September 26, 2025: OpenAI court-ordered retention ended
- Current: 30-day default retention with `store: true`

**Control Storage**:
```typescript
// Disable storage (no retention)
const response = await openai.responses.create({
  model: 'gpt-5',
  input: 'Hello!',
  store: false,  // ✅ No retention
});

// ZDR organizations: store always treated as false
const response = await openai.responses.create({
  model: 'gpt-5',
  input: 'Hello!',
  store: true,  // ⚠️ Ignored by OpenAI for ZDR orgs, treated as false
});
```

**ZDR Compliance**:
- Avoid background mode (requires temporary storage)
- Explicitly set `store: false` for clarity
- Note: 60s timeout applies in sync mode

**Source**: [OpenAI Data Controls](https://platform.openai.com/docs/guides/your-data)

---

## Polymorphic Outputs

Returns **8 output types** instead of single message:

| Type | Example |
|------|---------|
| `message` | Final answer, explanation |
| `reasoning` | Step-by-step thought process (free!) |
| `code_interpreter_call` | Python code + results |
| `mcp_call` | Tool name, args, output |
| `mcp_list_tools` | Tool definitions from MCP server |
| `file_search_call` | Matched chunks, citations |
| `web_search_call` | URLs, snippets |
| `image_generation_call` | Image URL |

**Processing:**
```typescript
response.output.forEach(item => {
  if (item.type === 'reasoning') console.log(item.summary[0].text);
  if (item.type === 'web_search_call') console.log(item.results);
  if (item.type === 'message') console.log(item.content[0].text);
});

// Or use helper for text-only
console.log(response.output_text);
```

---

## Migration from Chat Completions

**Breaking Changes:**

| Feature | Chat Completions | Responses API |
|---------|-----------------|---------------|
| Endpoint | `/v1/chat/completions` | `/v1/responses` |
| Parameter | `messages` | `input` |
| Role | `system` | `developer` |
| Output | `choices[0].message.content` | `output_text` |
| State | Manual array | Automatic (conversation ID) |
| Streaming | `data: {"choices":[...]}` | SSE with 8 item types |

**Example:**
```typescript
// Before
const response = await openai.chat.completions.create({
  model: 'gpt-5',
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'Hello!' },
  ],
});
console.log(response.choices[0].message.content);

// After
const response = await openai.responses.create({
  model: 'gpt-5',
  input: [
    { role: 'developer', content: 'You are a helpful assistant.' },
    { role: 'user', content: 'Hello!' },
  ],
});
console.log(response.output_text);
```

---

## Migration from Assistants API

**CRITICAL: Assistants API Sunset Timeline**

- **August 26, 2025**: Assistants API officially deprecated
- **2025-2026**: OpenAI providing migration utilities
- **August 26, 2026**: Assistants API sunset (stops working)

**Migrate before August 26, 2026** to avoid breaking changes.

**Source**: [Assistants API Sunset Announcement](https://community.openai.com/t/assistants-api-beta-deprecation-august-26-2026-sunset/1354666)

**Key Breaking Changes:**

| Assistants API | Responses API |
|----------------|---------------|
| Assistants (created via API) | Prompts (created in dashboard) |
| Threads | Conversations (store items, not just messages) |
| Runs (server-side lifecycle) | Responses (stateless calls) |
| Run-Steps | Items (polymorphic outputs) |

**Migration Example:**
```typescript
// Before (Assistants API - deprecated)
const assistant = await openai.beta.assistants.create({
  model: 'gpt-4',
  instructions: 'You are helpful.',
});

const thread = await openai.beta.threads.create();

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

// After (Responses API - current)
const conversation = await openai.conversations.create({
  metadata: { purpose: 'customer_support' },
});

const response = await openai.responses.create({
  model: 'gpt-5',
  conversation: conversation.id,
  input: [
    { role: 'developer', content: 'You are helpful.' },
    { role: 'user', content: 'Hello!' },
  ],
});
```

**Migration Guide**: [Official Assistants Migration Docs](https://platform.openai.com/docs/guides/migrate-to-responses)

---

## Known Issues Prevention

This skill prevents **11** documented errors:

**1. Session State Not Persisting**
- Cause: Not using conversation IDs or using different IDs per turn
- Fix: Create conversation once (`const conv = await openai.conversations.create()`), reuse `conv.id` for all turns

**2. MCP Server Connection Failed** (`mcp_connection_error`)
- Causes: Invalid URL, missing/expired auth token, server down
- Fix: Verify URL is correct, test manually with `fetch()`, check token expiration

**3. Code Interpreter Timeout** (`code_interpreter_timeout`)
- Cause: Code runs longer than 30 seconds
- Fix: Use `background: true` for extended timeout (up to 10 min)

**4. Image Generation Rate Limit** (`rate_limit_error`)
- Cause: Too many DALL-E requests
- Fix: Implement exponential backoff retry (1s, 2s, 3s delays)

**5. File Search Relevance Issues**
- Cause: Vague queries return irrelevant results
- Fix: Use specific queries ("pricing in Q4 2024" not "find pricing"), filter by `chunk.score > 0.7`

**6. Cost Tracking Confusion**
- Cause: Responses bills for input + output + tools + stored conversations (vs Chat Completions: input + output only)
- Fix: Set `store: false` if not needed, monitor `response.usage.tool_tokens`

**7. Conversation Not Found** (`invalid_request_error`)
- Causes: ID typo, conversation deleted, or expired (90-day limit)
- Fix: Verify exists with `openai.conversations.list()` before using

**8. Tool Output Parsing Failed**
- Cause: Accessing wrong output structure
- Fix: Use `response.output_text` helper or iterate `response.output.forEach(item => ...)` checking `item.type`

**9. Zod v4 Incompatibility with Structured Outputs**
- **Error**: `Invalid schema for response_format 'name': schema must be a JSON Schema of 'type: "object"', got 'type: "string"'.`
- **Source**: [GitHub Issue #1597](https://github.com/openai/openai-node/issues/1597)
- **Why It Happens**: SDK's vendored `zod-to-json-schema` library doesn't support Zod v4 (missing `ZodFirstPartyTypeKind` export)
- **Prevention**: Pin to Zod v3 (`"zod": "^3.23.8"`) or use custom `zodTextFormat` with `z.toJSONSchema({ target: "draft-7" })`

```typescript
// Workaround: Pin to Zod v3 (recommended)
{
  "dependencies": {
    "openai": "^6.16.0",
    "zod": "^3.23.8"  // DO NOT upgrade to v4 yet
  }
}
```

**10. Background Mode Web Search Missing Sources**
- **Error**: `web_search_call` output items contain query but no sources/results
- **Source**: [GitHub Issue #1676](https://github.com/openai/openai-node/issues/1676)
- **Why It Happens**: When using `background: true` + `web_search` tool, OpenAI doesn't return sources in the response
- **Prevention**: Use synchronous mode (`background: false`) when web search sources are needed

```typescript
// ✅ Sources available in sync mode
const response = await openai.responses.create({
  model: 'gpt-5',
  input: 'Latest AI news?',
  background: false,  // Required for sources
  tools: [{ type: 'web_search' }],
});
```

**11. Streaming Mode Missing output_text Helper**
- **Error**: `finalResponse().output_text` is `undefined` in streaming mode
- **Source**: [GitHub Issue #1662](https://github.com/openai/openai-node/issues/1662)
- **Why It Happens**: `stream.finalResponse()` doesn't include `output_text` convenience field (only available in non-streaming responses)
- **Prevention**: Listen for `output_text.done` event or manually extract from `output` items

```typescript
// Workaround: Listen for event
const stream = openai.responses.stream({ model: 'gpt-5', input: 'Hello!' });
let outputText = '';
for await (const event of stream) {
  if (event.type === 'output_text.done') {
    outputText = event.output_text;  // ✅ Available in event
  }
}
```

---

## Critical Patterns

**✅ Always:**
- Use conversation IDs for multi-turn (40-80% better cache)
- Handle all 8 output types in polymorphic responses
- Use `background: true` for tasks >30s
- Provide MCP `authorization` tokens (NOT stored, required each request)
- Monitor `response.usage.total_tokens` for cost control

**❌ Never:**
- Expose API keys in client-side code
- Assume single message output (use `response.output_text` helper)
- Reuse conversation IDs across users (security risk)
- Ignore error types (handle `rate_limit_error`, `mcp_connection_error` specifically)
- Poll faster than 1s for background tasks (use 5s intervals)

---

## References

**Official Docs:**
- Responses API Guide: https://platform.openai.com/docs/guides/responses
- API Reference: https://platform.openai.com/docs/api-reference/responses
- MCP Integration: https://platform.openai.com/docs/guides/tools-connectors-mcp
- Blog Post: https://developers.openai.com/blog/responses-api/
- Starter App: https://github.com/openai/openai-responses-starter-app

**Skill Resources:** `templates/`, `references/responses-vs-chat-completions.md`, `references/mcp-integration-guide.md`, `references/built-in-tools-guide.md`, `references/migration-guide.md`, `references/top-errors.md`

---

**Last verified**: 2026-01-21 | **Skill version**: 2.1.0 | **Changes**: Added 3 TIER 1 issues (Zod v4, background web search, streaming output_text), 2 TIER 2 findings (MCP approval, reasoning privacy), Data Retention & ZDR section, Assistants API sunset timeline, background mode TTFT note, web search TypeScript limitation. Updated SDK version to 6.16.0.

```

### references/top-errors.md

```markdown
# Top 12 Errors - OpenAI Assistants API

**Last Updated**: 2025-10-25

This document catalogs the most common errors encountered when working with the Assistants API v2 and their solutions.

---

## 1. Thread Already Has Active Run

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

**Cause**: Trying to create a new run or add messages while another run is still processing (status: `queued`, `in_progress`, or `cancelling`).

**Solution**:
```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)) {
    // Wait for completion or cancel
    await openai.beta.threads.runs.cancel(threadId, latestRun.id);

    // Poll until cancelled
    let run = latestRun;
    while (run.status !== 'cancelled') {
      await new Promise(resolve => setTimeout(resolve, 500));
      run = await openai.beta.threads.runs.retrieve(threadId, run.id);
    }
  }
}
```

**Prevention**: Always check for active runs before creating new ones.

**Source**: [OpenAI Community](https://community.openai.com/t/error-running-thread-already-has-an-active-run/782118)

---

## 2. Run Polling Timeout

**Error**: Run never completes within reasonable polling window (300+ seconds).

**Cause**: Long-running tasks (complex code execution, large file processing) exceed expected completion time.

**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 of ${maxSeconds}s`);
    }

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

**Prevention**: Set appropriate timeouts and use streaming for better UX.

---

## 3. Vector Store Indexing Delay

**Error**: File search returns no results despite files being uploaded.

**Cause**: Using vector store before indexing completes (async process).

**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);
    console.log(`Indexing: ${store.file_counts.completed}/${store.file_counts.total}`);
  }

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

  return store; // status: 'completed'
}
```

**Prevention**: Always wait for `status: "completed"` before using vector store with assistants.

**Source**: [OpenAI Community](https://community.openai.com/t/assistants-api-file-search-and-vector-stores/863944)

---

## 4. File Search Relevance Issues

**Error**: File search returns irrelevant or incomplete results.

**Cause**: Poor document chunking, lack of context, or query optimization needed.

**Solution**:
- **Better instructions**: Guide assistant on how to use file search
- **Structured documents**: Use clear headers, sections, and formatting
- **Metadata**: Add descriptive metadata to files (coming soon)
- **Query refinement**: Encourage users to be specific

```typescript
const assistant = await openai.beta.assistants.create({
  instructions: `You are a support assistant. When answering:
    1. Use file_search to find relevant documentation
    2. Quote specific sections with citations
    3. If information isn't found, say so clearly
    4. Provide context around the answer`,
  tools: [{ type: "file_search" }],
  // ...
});
```

**Prevention**: Structure documents well and provide clear assistant instructions.

---

## 5. Code Interpreter File Output Not Found

**Error**: `image_file.file_id` referenced but file doesn't exist or can't be downloaded.

**Cause**: Files generated by Code Interpreter are temporary and may be cleaned up before retrieval.

**Solution**:
```typescript
// Retrieve and save immediately after run completes
const messages = await openai.beta.threads.messages.list(threadId);
const responseMessage = messages.data[0];

for (const content of responseMessage.content) {
  if (content.type === 'image_file') {
    try {
      const fileData = await openai.files.content(content.image_file.file_id);
      const buffer = Buffer.from(await fileData.arrayBuffer());
      fs.writeFileSync(`output_${content.image_file.file_id}.png`, buffer);
    } catch (error) {
      console.error('File no longer available:', error);
    }
  }
}
```

**Prevention**: Download generated files immediately after run completion.

**Source**: [Medium - Code Interpreter Tutorial](https://tmmtt.medium.com/openai-assistant-api-with-code-interpreter-e7f382bff83e)

---

## 6. Thread Message Limit Exceeded

**Error**: `400 Thread has exceeded the maximum number of messages (100,000)`.

**Cause**: Very long conversations hitting the 100k message limit.

**Solution**:
```typescript
async function archiveAndStartNewThread(oldThreadId: string, userId: string) {
  // Get conversation summary
  const messages = await openai.beta.threads.messages.list(oldThreadId, {
    limit: 50,
  });

  // Save to database
  await db.archiveThread(oldThreadId, messages.data);

  // Create new thread
  const newThread = await openai.beta.threads.create({
    metadata: {
      user_id: userId,
      previous_thread: oldThreadId,
    },
  });

  return newThread.id;
}
```

**Prevention**: Archive old threads and create new ones periodically.

---

## 7. Function Calling Timeout

**Error**: Run expires (status: `expired`) while waiting for tool outputs.

**Cause**: Tool execution takes too long (max 10 minutes for run).

**Solution**:
```typescript
if (run.status === 'requires_action') {
  const toolCalls = run.required_action.submit_tool_outputs.tool_calls;
  const toolOutputs = [];

  for (const toolCall of toolCalls) {
    try {
      // Add timeout to function execution
      const output = await Promise.race([
        executeFunction(toolCall.function.name, JSON.parse(toolCall.function.arguments)),
        new Promise((_, reject) =>
          setTimeout(() => reject(new Error('Function timeout')), 30000)
        ),
      ]);

      toolOutputs.push({
        tool_call_id: toolCall.id,
        output: JSON.stringify(output),
      });
    } catch (error) {
      // Return error as output
      toolOutputs.push({
        tool_call_id: toolCall.id,
        output: JSON.stringify({ error: error.message }),
      });
    }
  }

  await openai.beta.threads.runs.submitToolOutputs(threadId, run.id, {
    tool_outputs: toolOutputs,
  });
}
```

**Prevention**: Implement timeouts on function execution and return errors gracefully.

---

## 8. Streaming Run Interruption

**Error**: Stream connection closes unexpectedly or events stop arriving.

**Cause**: Network issues, server errors, or run failures.

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

    for await (const event of stream) {
      // Handle events
      if (event.event === 'error') {
        throw new Error('Stream error');
      }
    }
  } catch (error) {
    console.error('Stream interrupted:', error);

    // Fall back to polling
    const runs = await openai.beta.threads.runs.list(threadId, {
      limit: 1,
      order: 'desc',
    });

    const run = runs.data[0];
    return pollRunCompletion(threadId, run.id);
  }
}
```

**Prevention**: Implement fallback to polling if streaming fails.

**Source**: [OpenAI Community](https://community.openai.com/t/streaming-stopped-at-thread-run-requires-action-when-handling-openai-assistants-function-calling/943674)

---

## 9. Vector Store Quota Limits

**Error**: `429 Rate limit reached for vector store operations`.

**Cause**: Too many vector store operations or storage exceeded.

**Solution**:
- **Monitor storage**: Check `usage_bytes` regularly
- **Delete unused stores**: Clean up old vector stores
- **Batch operations**: Use batch file uploads instead of individual uploads

```typescript
async function cleanupOldVectorStores(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);
      console.log(`Deleted vector store: ${store.id}`);
    }
  }
}
```

**Prevention**: Set auto-expiration on vector stores and monitor costs.

---

## 10. File Upload Format Incompatibility

**Error**: `400 Unsupported file type for this tool`.

**Cause**: Uploading file format not supported by the tool.

**Solution**:
```typescript
const 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'
  ],
};

function validateFileFormat(filename: string, tool: 'code_interpreter' | 'file_search') {
  const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();

  if (!SUPPORTED_FORMATS[tool].includes(ext)) {
    throw new Error(`Unsupported file format for ${tool}: ${ext}`);
  }
}

// Validate before upload
validateFileFormat('data.csv', 'code_interpreter'); // OK
validateFileFormat('video.mp4', 'file_search'); // Throws error
```

**Prevention**: Validate file formats before uploading.

---

## 11. Assistant Instructions Token Limit

**Error**: `400 Instructions exceed maximum length`.

**Cause**: Instructions field exceeds 256,000 characters (v2 limit).

**Solution**:
- **Use file search**: Put long instructions in documents
- **Concise instructions**: Be clear and brief
- **System messages**: Use thread-level messages for context

```typescript
// ❌ Bad: Very long instructions
const assistant = await openai.beta.assistants.create({
  instructions: "..." // 300k characters
});

// ✅ Good: Concise instructions + file search
const assistant = await openai.beta.assistants.create({
  instructions: "You are a support assistant. Use file_search to find answers in the knowledge base.",
  tools: [{ type: "file_search" }],
  tool_resources: {
    file_search: {
      vector_store_ids: [vectorStoreId], // Long content here
    },
  },
});
```

**Prevention**: Keep instructions under 256k characters; use file search for knowledge.

---

## 12. Thread Deletion While Run Active

**Error**: `400 Cannot delete thread while run is active`.

**Cause**: Attempting to delete a thread that has an active run.

**Solution**:
```typescript
async function safeDeleteThread(threadId: string) {
  // Cancel active runs first
  const runs = await openai.beta.threads.runs.list(threadId);

  for (const run of runs.data) {
    if (['queued', 'in_progress'].includes(run.status)) {
      await openai.beta.threads.runs.cancel(threadId, run.id);

      // Wait for cancellation
      let runStatus = run;
      while (runStatus.status !== 'cancelled') {
        await new Promise(resolve => setTimeout(resolve, 500));
        runStatus = await openai.beta.threads.runs.retrieve(threadId, run.id);
      }
    }
  }

  // Now safe to delete
  await openai.beta.threads.del(threadId);
}
```

**Prevention**: Cancel all active runs before deleting threads.

---

## Quick Reference

| Error | Quick Fix |
|-------|-----------|
| Thread has active run | Cancel or wait for run completion |
| Polling timeout | Set timeout and cancel long runs |
| Vector store not ready | Wait for `status: "completed"` |
| File search no results | Check indexing complete, improve queries |
| Code Interpreter file lost | Download immediately after run |
| 100k message limit | Archive old threads, start new ones |
| Function timeout | Add timeouts to function execution |
| Stream interrupted | Fall back to polling |
| Vector store quota | Clean up old stores, use batch uploads |
| Unsupported file format | Validate file extensions before upload |
| Instructions too long | Use file search for knowledge |
| Can't delete thread | Cancel active runs first |

---

**Additional Resources**:
- [OpenAI Assistants API Docs](https://platform.openai.com/docs/assistants)
- [OpenAI Community Forum](https://community.openai.com/c/api/assistants-api/49)
- [API Reference](https://platform.openai.com/docs/api-reference/assistants)

```

### references/migration-from-v1.md

```markdown
# Migration from Assistants API v1 to v2

**v1 Deprecated**: December 18, 2024 (no longer accessible)
**v2 Status**: Production (Deprecated H1 2026 in favor of Responses API)

---

## Breaking Changes

### 1. Retrieval Tool → File Search

**v1:**
```typescript
{
  tools: [{ type: "retrieval" }],
  file_ids: ["file_abc123", "file_def456"]
}
```

**v2:**
```typescript
{
  tools: [{ type: "file_search" }],
  tool_resources: {
    file_search: {
      vector_store_ids: ["vs_abc123"]
    }
  }
}
```

**Action**: Create vector stores and migrate files.

### 2. File Attachments

**v1**: Files attached at assistant level
**v2**: Files attached at message level

**v1:**
```typescript
const assistant = await openai.beta.assistants.create({
  file_ids: ["file_abc123"],
});
```

**v2:**
```typescript
await openai.beta.threads.messages.create(thread.id, {
  content: "...",
  attachments: [{
    file_id: "file_abc123",
    tools: [{ type: "code_interpreter" }]
  }],
});
```

### 3. Instructions Character Limit

- **v1**: 32,000 characters
- **v2**: 256,000 characters (8x increase)

---

## Migration Steps

### Step 1: Create Vector Stores

```typescript
// Old v1 approach
const assistant = await openai.beta.assistants.create({
  tools: [{ type: "retrieval" }],
  file_ids: fileIds, // Direct attachment
});

// New v2 approach
const vectorStore = await openai.beta.vectorStores.create({
  name: "Knowledge Base",
});

await openai.beta.vectorStores.fileBatches.create(vectorStore.id, {
  file_ids: fileIds,
});

const assistant = await openai.beta.assistants.create({
  tools: [{ type: "file_search" }],
  tool_resources: {
    file_search: {
      vector_store_ids: [vectorStore.id],
    },
  },
});
```

### Step 2: Update File Attachments

```typescript
// Move file attachments from assistant to messages
await openai.beta.threads.messages.create(thread.id, {
  role: "user",
  content: "Analyze this file",
  attachments: [{
    file_id: "file_abc123",
    tools: [{ type: "code_interpreter" }],
  }],
});
```

### Step 3: Test Thoroughly

- Verify file search returns expected results
- Check Code Interpreter file handling
- Test streaming if used
- Validate function calling patterns

---

## New v2 Features

### 1. Massive File Capacity

- **v1**: ~20 files per assistant
- **v2**: 10,000 files per assistant (500x increase)

### 2. Better Search Performance

- Vector + keyword search
- Parallel query processing
- Advanced reranking

### 3. Auto-Expiration

```typescript
const vectorStore = await openai.beta.vectorStores.create({
  expires_after: {
    anchor: "last_active_at",
    days: 30,
  },
});
```

### 4. Batch File Operations

```typescript
const batch = await openai.beta.vectorStores.fileBatches.create(vectorStoreId, {
  file_ids: ["file_1", "file_2", "file_3"],
});
```

---

## Cost Implications

### v1 (Deprecated)
- No separate storage costs for retrieval

### v2
- **Storage**: $0.10/GB/day for vector stores
- **Free tier**: First 1GB
- **Optimization**: Use auto-expiration

---

## Recommended Path Forward

**For existing v1 applications:**
1. Migrate to v2 immediately (v1 no longer works)
2. Plan migration to Responses API (v2 sunset in H1 2026)

**For new applications:**
- ✅ Use [Responses API](../../openai-responses/SKILL.md)
- ❌ Don't use Assistants API (being deprecated)

---

## Migration Checklist

- [ ] Update to openai SDK 6.7.0+
- [ ] Create vector stores for file search
- [ ] Migrate file attachments to message level
- [ ] Test file search results
- [ ] Update to `file_search` from `retrieval`
- [ ] Implement vector store cleanup
- [ ] Monitor storage costs
- [ ] Plan migration to Responses API

---

**Last Updated**: 2025-10-25

```

### templates/basic-assistant.ts

```typescript
/**
 * Basic Assistant Example
 *
 * Demonstrates the fundamental workflow:
 * 1. Create an assistant
 * 2. Create a thread
 * 3. Add a message
 * 4. Create a run
 * 5. Poll for completion
 * 6. Retrieve the response
 */

import OpenAI from 'openai';

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

async function main() {
  console.log('🤖 Creating Math Tutor Assistant...\n');

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

  console.log(`✅ Assistant created: ${assistant.id}\n`);

  // 2. Create a thread
  const thread = await openai.beta.threads.create();
  console.log(`✅ Thread created: ${thread.id}\n`);

  // 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?",
  });

  console.log('✅ Message added to thread\n');

  // 4. Create a run
  console.log('🏃 Creating run...\n');
  const run = await openai.beta.threads.runs.create(thread.id, {
    assistant_id: assistant.id,
  });

  // 5. Poll for completion
  console.log('⏳ Waiting for completion...\n');
  let runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);

  while (runStatus.status !== 'completed') {
    if (runStatus.status === 'failed' || runStatus.status === 'cancelled') {
      console.error(`❌ Run ${runStatus.status}:`, runStatus.last_error);
      process.exit(1);
    }

    await new Promise(resolve => setTimeout(resolve, 1000));
    runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
    console.log(`   Status: ${runStatus.status}`);
  }

  console.log('\n✅ Run completed!\n');

  // 6. Retrieve messages
  const messages = await openai.beta.threads.messages.list(thread.id);

  console.log('💬 Response:\n');
  const response = messages.data[0].content[0];
  if (response.type === 'text') {
    console.log(response.text.value);
  }

  // Usage stats
  console.log('\n📊 Usage:');
  console.log(`   Prompt tokens: ${runStatus.usage?.prompt_tokens}`);
  console.log(`   Completion tokens: ${runStatus.usage?.completion_tokens}`);
  console.log(`   Total tokens: ${runStatus.usage?.total_tokens}`);

  // Cleanup (optional)
  // await openai.beta.assistants.del(assistant.id);
  // await openai.beta.threads.del(thread.id);
}

main().catch(console.error);

```

openai-assistants | SkillHub