# Warlock AI Openai — full skills

> Package: `@warlock.js/ai-openai`

> Generated artifact. Concatenates every SKILL.md and reference file under `@warlock.js/ai-openai/skills/`. Re-run `node scripts/generate-llms.mjs` after any change.

## setup-openai  `@warlock.js/ai-openai/setup-openai/SKILL.md`

---
name: setup-openai
description: 'Wire @warlock.js/ai-openai — new OpenAISDK({apiKey, baseURL?, provider?, pricing?}) for OpenAI / Azure / OpenRouter, .model({name, vision?, structuredOutput?, responseFormat?}) for ModelContract, .embedder({name, dimensions?}) for embeddings. Triggers: `OpenAISDK`, `.model`, `.embedder`, `.embed`, `.embedMany`, `baseURL`, `pricing`, `responseSchema`, `responseFormat`; "wire openai into a warlock agent", "configure gpt-4o", "route through openrouter or azure openai", "openai embeddings with warlock"; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: agent wiring — `@warlock.js/ai/run-ai-agent/SKILL.md`; adapter comparison — `@warlock.js/ai/pick-ai-provider/SKILL.md`; competing adapters `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `openai` SDK, Vercel `@ai-sdk/openai`.'
---

# `@warlock.js/ai-openai`

Provider adapter that turns OpenAI Chat Completions into a vendor-neutral `ModelContract`. Pair with `@warlock.js/ai` for the agent / tool / system-prompt surface.

## Construction

```ts
import { OpenAISDK } from "@warlock.js/ai-openai";

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

// OpenAI-compatible endpoints (Azure OpenAI, OpenRouter, local gateways).
// Pass `provider` to label the upstream — flows through to
// `ModelContract.provider`, `AgentReport.model.provider`, and logs.
const openrouter = new OpenAISDK({
  apiKey: process.env.OPENROUTER_API_KEY!,
  baseURL: "https://openrouter.ai/api/v1",
  provider: "openrouter",
});
```

`OpenAISDK` is a class (not a factory) — adapter entry points hold a long-lived `OpenAI` client and align with `new OpenAI(...)` upstream convention.

`provider` defaults to `"openai"`. It's an SDK-level identity (one client = one upstream), not a per-model knob.

## Producing a model

```ts
openai.model({ name: "gpt-4o-mini" })                       // common case
openai.model({ name: "gpt-4o", temperature: 0.2 })          // sampling controls
openai.model({ name: "fine-tuned-x", vision: true })        // explicit capability override
```

Returns a `ModelContract` you pass straight into `ai.agent({ model })`.

## Capabilities — what's auto-set

| Flag | Default |
| --- | --- |
| `structuredOutput` | `true`, unless `responseFormat` is set to `"json_object"` or `"text"` (loose modes) — then `false`. |
| `vision` | Inferred from model name. `true` for `gpt-4o*`, `gpt-4-turbo*`, `gpt-4.1*`, `o1*`, `o3*`, `chatgpt-4o*`; `false` otherwise. |

**Override either flag explicitly** via `.model({ name, vision?, structuredOutput? })` — an explicit value always wins over inference.

## Structured output

When the agent passes `responseSchema` (a JSON Schema object), the adapter converts to `response_format: json_schema, strict: true` on the wire — token-level enforcement. Non-object schemas fall back to loose `json_object` mode (still valid JSON, no shape enforcement).

Some targets reject strict `json_schema` (older OpenAI models, OpenRouter routes, Ollama OpenAI-compat). Override the wire mode per model with `responseFormat`:

```ts
openai.model({ name: "legacy-model", responseFormat: "json_object" }) // valid JSON, no strict shape
openai.model({ name: "some-route",   responseFormat: "text" })        // no response_format on the wire
```

`"json_object"` and `"text"` also flip `structuredOutput` to `false`, so the agent re-injects the schema as a soft prompt hint. Pin `structuredOutput` explicitly to override that.

## Multipart messages (vision)

`ContentPart[]` user content is rendered into OpenAI's content-parts shape:

- `{ type: "text", text }` → `{ type: "text", text }`
- `{ type: "image", source: { url } }` → `{ type: "image_url", image_url: { url } }`
- `{ type: "image", source: { base64, mediaType } }` → `{ type: "image_url", image_url: { url: "data:{mediaType};base64,{base64}" } }`

The agent prepares attachments before they reach the adapter; this package never reads files itself.

## Streaming

`model.stream()` drains `chat.completions.create({ stream: true })` and yields `{ type: "delta", content }` per token, then — after the stream closes — one consolidated `{ type: "tool-call", id, name, input }` per requested tool, then a terminal `{ type: "done", finishReason, usage }`. `stream_options: { include_usage: true }` is enabled by default.

Tool-call argument fragments arrive split across deltas; the adapter accumulates them per `tool_calls[n].index` and parses the assembled JSON once on completion, so streamed tool calls round-trip identically to non-streaming `complete()`. Accumulators that never received a function name are skipped.

## Embeddings

```ts
const embedder = openai.embedder({ name: "text-embedding-3-small" });

const { vector, dimensions, usage } = await embedder.embed("Hello world");
const { vectors } = await embedder.embedMany(["doc 1", "doc 2", "doc 3"]);
```

`dimensions` is lazy — starts at `0`, populated from the first response. Pass `dimensions` in config to request output truncation (supported by `text-embedding-3-*`):

```ts
openai.embedder({ name: "text-embedding-3-large", dimensions: 256 });
```

## Token counting

```ts
await openai.count("some text")  // approximate (heuristic, not tiktoken)
```

Good enough for budgeting; not for billing.

## Pricing — per-model registry

`pricing` is a **registry keyed by model name** — one entry per model, all rates in **USD per 1,000,000 tokens** (`ModelPricing`: `input`, `output`, optional `cachedInput` / `cachedOutput`).

```ts
const openai = new OpenAISDK({
  apiKey,
  pricing: {
    // USD per 1M tokens.
    "gpt-4o-mini": { input: 0.15, output: 0.6, cachedInput: 0.075 },
    "gpt-4o":      { input: 2.5,  output: 10,  cachedInput: 1.25 },
  },
});

const { usage } = await ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) }).execute("hi");
usage.cost;  // { input, output, cachedInput?, cachedOutput? } — per-channel USD breakdown of THIS run
```

The registry is per-model; `usage.cost` is the per-channel breakdown the framework computes from `tokens × pricing[model]`. Resolution at `model()` time: per-model `pricing` (`openai.model({ name, pricing })`) > SDK registry > `undefined` (no cost computed). See [`@warlock.js/ai/pick-ai-provider/SKILL.md`](@warlock.js/ai/pick-ai-provider/SKILL.md).

## Errors

Raw OpenAI SDK errors are wrapped into the typed `@warlock.js/ai` `AIError` hierarchy via the adapter's error wrapper. Dispatch keys on `APIError.status + code` combined — see [`@warlock.js/ai/handle-ai-errors/SKILL.md`](@warlock.js/ai/handle-ai-errors/SKILL.md).

## When NOT to use this skill

- Direct calls to the `openai` SDK without going through `@warlock.js/ai` agents.
- Anthropic models — use `@warlock.js/ai-anthropic`. Bedrock — `@warlock.js/ai-bedrock`. Gemini — `@warlock.js/ai-google`. Ollama — `@warlock.js/ai-ollama`.

## See also

- [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — passing the model into `ai.agent({...})`
- [`@warlock.js/ai/embed-text/SKILL.md`](@warlock.js/ai/embed-text/SKILL.md) — embedder usage
- [`@warlock.js/ai/pick-ai-provider/SKILL.md`](@warlock.js/ai/pick-ai-provider/SKILL.md) — adapter comparison


