# OpenAI Client

Interact with OpenAI's APIs for chat completions, embeddings, image generation, and more.

## Methods

| Method                                   | Description                                        |
| ---------------------------------------- | -------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any OpenAI API request with schema validation |

## Usage

### Step 1: List Available Models

Model availability depends on your OpenAI account and API tier. Before making requests, validate which models you can access:

```typescript
import { api, z, openai } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_OPENAI = "a1b2c3d4-5678-90ab-cdef-333333333333";

const ModelsResponseSchema = z.object({
  data: z.array(
    z.object({
      id: z.string(),
      object: z.string(),
    }),
  ),
});

const models = await ctx.integrations.ai.apiRequest(
  {
    method: "GET",
    path: "/models",
  },
  { response: ModelsResponseSchema },
);

// Use a model ID from the response in subsequent requests
const availableModelId = models.data[0]?.id;
```

### Chat Completion

```typescript
const ChatCompletionResponseSchema = z.object({
  id: z.string(),
  object: z.literal("chat.completion"),
  created: z.number(),
  model: z.string(),
  choices: z.array(
    z.object({
      index: z.number(),
      message: z.object({
        role: z.string(),
        content: z.string().nullable(),
      }),
      finish_reason: z.string().nullable(),
    }),
  ),
  usage: z.object({
    prompt_tokens: z.number(),
    completion_tokens: z.number(),
    total_tokens: z.number(),
  }),
});

export default api({
  name: "UopenaiUv2Example",
  integrations: {
    ai: openai(PROD_OPENAI),
  },
  input: z.object({
    prompt: z.string(),
  }),
  output: z.object({
    response: z.string(),
  }),
  async run(ctx, { prompt }) {
    const result = await ctx.integrations.ai.apiRequest(
      {
        method: "POST",
        path: "/chat/completions",
        body: {
          model: "gpt-4",
          messages: [
            { role: "system", content: "You are a helpful assistant." },
            { role: "user", content: prompt },
          ],
          temperature: 0.7,
        },
      },
      { response: ChatCompletionResponseSchema },
    );

    return { response: result.choices[0]?.message.content ?? "" };
  },
});
```

### Chat Completion with Function Calling

```typescript
const FunctionCallResponseSchema = z.object({
  id: z.string(),
  choices: z.array(
    z.object({
      message: z.object({
        role: z.string(),
        content: z.string().nullable(),
        tool_calls: z
          .array(
            z.object({
              id: z.string(),
              type: z.literal("function"),
              function: z.object({
                name: z.string(),
                arguments: z.string(), // JSON string
              }),
            }),
          )
          .optional(),
      }),
      finish_reason: z.string().nullable(),
    }),
  ),
});

const result = await ctx.integrations.ai.apiRequest(
  {
    method: "POST",
    path: "/chat/completions",
    body: {
      model: "gpt-4",
      messages: [
        { role: "user", content: "What's the weather in San Francisco?" },
      ],
      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" },
              },
              required: ["location"],
            },
          },
        },
      ],
    },
  },
  { response: FunctionCallResponseSchema },
);

const toolCall = result.choices[0]?.message.tool_calls?.[0];
if (toolCall) {
  const args = JSON.parse(toolCall.function.arguments);
  console.log(`Function: ${toolCall.function.name}, Args:`, args);
}
```

### Generate Embeddings

```typescript
const EmbeddingsResponseSchema = z.object({
  object: z.literal("list"),
  data: z.array(
    z.object({
      object: z.literal("embedding"),
      index: z.number(),
      embedding: z.array(z.number()),
    }),
  ),
  model: z.string(),
  usage: z.object({
    prompt_tokens: z.number(),
    total_tokens: z.number(),
  }),
});

const embeddings = await ctx.integrations.ai.apiRequest(
  {
    method: "POST",
    path: "/embeddings",
    body: {
      model: "text-embedding-3-small",
      input: ["Hello world", "How are you?"],
    },
  },
  { response: EmbeddingsResponseSchema },
);

embeddings.data.forEach((item) => {
  console.log(`Index ${item.index}: ${item.embedding.length} dimensions`);
});
```

### Image Generation (DALL-E)

```typescript
const ImageResponseSchema = z.object({
  created: z.number(),
  data: z.array(
    z.object({
      url: z.string().optional(),
      b64_json: z.string().optional(),
      revised_prompt: z.string().optional(),
    }),
  ),
});

const images = await ctx.integrations.ai.apiRequest(
  {
    method: "POST",
    path: "/images/generations",
    body: {
      model: "dall-e-3",
      prompt: "A serene mountain landscape at sunset",
      n: 1,
      size: "1024x1024",
      quality: "standard",
    },
  },
  { response: ImageResponseSchema },
);

const imageUrl = images.data[0]?.url;
```

### Text-to-Speech

```typescript
// Note: Audio endpoints return binary data
// This example shows the request structure
const ttsResult = await ctx.integrations.ai.apiRequest(
  {
    method: "POST",
    path: "/audio/speech",
    body: {
      model: "tts-1",
      input: "Hello, how are you today?",
      voice: "alloy",
    },
  },
  { response: z.unknown() }, // Binary response
);
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### Streaming Is Not Supported

`apiRequest()` does not support streaming or Server-Sent Events. Do not set
`stream: true` — streaming responses fail schema validation. Every call
returns the complete response; if a UI needs real-time token streaming,
handle it at the frontend layer, not through the SDK.

### No Specialized Methods

The OpenAI client only provides `apiRequest()`. There are no other specialized methods:

```typescript
// WRONG - These methods do not exist
await openai.createChatCompletion({ ... });
await openai.createEmbedding({ ... });

// CORRECT - Use apiRequest for all operations
await ctx.integrations.ai.apiRequest(
  { method: "POST", path: "/chat/completions", body: { ... } },
  { response: ResponseSchema }
);
```

### Response Schema is Required

You must always provide a response schema:

```typescript
// WRONG - Missing response schema
const result = await ctx.integrations.ai.apiRequest({
  method: "POST",
  path: "/chat/completions",
  body: { ... },
});

// CORRECT - Include response schema
const result = await ctx.integrations.ai.apiRequest(
  { method: "POST", path: "/chat/completions", body: { ... } },
  { response: ChatCompletionResponseSchema }
);
```

### Content Can Be Null

Chat completion content can be `null` when using function calling:

```typescript
// WRONG - Assumes content is always a string
const schema = z.object({
  choices: z.array(
    z.object({
      message: z.object({
        content: z.string(), // Will fail if null
      }),
    }),
  ),
});

// CORRECT - Allow null content
const schema = z.object({
  choices: z.array(
    z.object({
      message: z.object({
        content: z.string().nullable(),
      }),
    }),
  ),
});
```

### API Version Headers for Beta Features

Some features require beta headers:

```typescript
// Using Assistants API (beta)
const result = await ctx.integrations.ai.apiRequest(
  {
    method: "POST",
    path: "/assistants",
    body: { ... },
    headers: {
      "OpenAI-Beta": "assistants=v2",
    },
  },
  { response: AssistantResponseSchema }
);
```

### Handling Rate Limits

OpenAI has rate limits. Consider implementing retry logic:

```typescript
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      // Wait with exponential backoff
      await new Promise((r) => setTimeout(r, Math.pow(2, i) * 1000));
    }
  }
  throw new Error("Max retries exceeded");
}

const result = await withRetry(() =>
  ctx.integrations.ai.apiRequest(
    { method: "POST", path: "/chat/completions", body: { ... } },
    { response: ChatCompletionResponseSchema }
  )
);
```

### Token Limits

Be aware of model token limits:

| Model         | Max Tokens |
| ------------- | ---------- |
| gpt-4         | 8,192      |
| gpt-4-32k     | 32,768     |
| gpt-4-turbo   | 128,000    |
| gpt-3.5-turbo | 16,385     |

```typescript
// Set max_tokens to control response length
const result = await ctx.integrations.ai.apiRequest(
  {
    method: "POST",
    path: "/chat/completions",
    body: {
      model: "gpt-4",
      messages: [...],
      max_tokens: 1000, // Limit response tokens
    },
  },
  { response: ChatCompletionResponseSchema }
);
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.ai.apiRequest(
    { method: "POST", path: "/chat/completions", body: { ... } },
    { response: ChatCompletionResponseSchema }
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Response validation failed:", error.details.zodError);
    console.error("Actual response:", error.details.data);
  }
}
```

## API Reference

- [OpenAI API Reference](https://platform.openai.com/docs/api-reference)
- [Chat Completions](https://platform.openai.com/docs/api-reference/chat)
- [Embeddings](https://platform.openai.com/docs/api-reference/embeddings)
- [Images](https://platform.openai.com/docs/api-reference/images)
