@warlock.js/ai-openai 5.29.0 → 5.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -1
- package/README.md +3 -3
- package/cjs/index.cjs +437 -69
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +6 -4
- package/esm/config.type.d.mts.map +1 -1
- package/esm/embedder.d.mts +1 -1
- package/esm/embedder.d.mts.map +1 -1
- package/esm/embedder.mjs +1 -1
- package/esm/embedder.mjs.map +1 -1
- package/esm/image.d.mts +1 -1
- package/esm/image.d.mts.map +1 -1
- package/esm/image.mjs +1 -1
- package/esm/image.mjs.map +1 -1
- package/esm/index.d.mts +3 -1
- package/esm/index.mjs +2 -1
- package/esm/known-pricing.d.mts +35 -0
- package/esm/known-pricing.d.mts.map +1 -0
- package/esm/known-pricing.mjs +117 -0
- package/esm/known-pricing.mjs.map +1 -0
- package/esm/known-reasoning-models.mjs +1 -1
- package/esm/known-reasoning-models.mjs.map +1 -1
- package/esm/known-vision-models.mjs +1 -1
- package/esm/known-vision-models.mjs.map +1 -1
- package/esm/model.mjs +76 -24
- package/esm/model.mjs.map +1 -1
- package/esm/provider-options.d.mts +8 -0
- package/esm/provider-options.d.mts.map +1 -0
- package/esm/provider-options.mjs +49 -0
- package/esm/provider-options.mjs.map +1 -0
- package/esm/responses-model.mjs +117 -18
- package/esm/responses-model.mjs.map +1 -1
- package/esm/sdk.d.mts +4 -3
- package/esm/sdk.d.mts.map +1 -1
- package/esm/sdk.mjs +6 -4
- package/esm/sdk.mjs.map +1 -1
- package/esm/speech.d.mts +1 -1
- package/esm/speech.d.mts.map +1 -1
- package/esm/speech.mjs +1 -1
- package/esm/speech.mjs.map +1 -1
- package/esm/transcription.d.mts +1 -1
- package/esm/transcription.d.mts.map +1 -1
- package/esm/transcription.mjs +1 -1
- package/esm/transcription.mjs.map +1 -1
- package/esm/utils/build-usage.mjs +15 -3
- package/esm/utils/build-usage.mjs.map +1 -1
- package/esm/utils/index.mjs +1 -1
- package/esm/utils/map-finish-reason.mjs +1 -1
- package/esm/utils/map-finish-reason.mjs.map +1 -1
- package/esm/utils/map-responses-status.mjs +1 -1
- package/esm/utils/map-responses-status.mjs.map +1 -1
- package/esm/utils/prompt-cache-params.mjs +26 -7
- package/esm/utils/prompt-cache-params.mjs.map +1 -1
- package/esm/utils/reasoning-effort.mjs +1 -1
- package/esm/utils/reasoning-effort.mjs.map +1 -1
- package/esm/utils/reasoning-replay.mjs +24 -2
- package/esm/utils/reasoning-replay.mjs.map +1 -1
- package/esm/utils/structured-output.mjs +1 -1
- package/esm/utils/structured-output.mjs.map +1 -1
- package/esm/utils/to-openai-messages.mjs +1 -1
- package/esm/utils/to-openai-messages.mjs.map +1 -1
- package/esm/utils/to-openai-tools.mjs +1 -1
- package/esm/utils/to-openai-tools.mjs.map +1 -1
- package/esm/utils/to-responses-input.mjs +1 -1
- package/esm/utils/to-responses-input.mjs.map +1 -1
- package/esm/utils/to-responses-tools.mjs +1 -1
- package/esm/utils/to-responses-tools.mjs.map +1 -1
- package/esm/utils/wrap-openai-error.mjs +7 -2
- package/esm/utils/wrap-openai-error.mjs.map +1 -1
- package/llms-full.txt +128 -37
- package/llms.txt +1 -1
- package/package.json +5 -4
- package/skills/setup-openai/SKILL.md +128 -37
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: setup-openai
|
|
3
|
-
description: 'Wire @warlock.js/ai-openai — new OpenAISDK({apiKey, baseURL?, provider?, pricing?}) for OpenAI / Azure / OpenRouter, .model({name, vision?, reasoning?, structuredOutput?, responseFormat?, pdf?, audio?}) for ModelContract, .embedder({name, dimensions?}) for embeddings, .image({name, pricing?}) for gpt-image-*/dall-e-* image generation (via ai.image). PDF input maps to OpenAI file parts (opt-in pdf:true), audio input to input_audio (opt-in audio:true). Triggers: `OpenAISDK`, `.model`, `.embedder`, `.image`, `.embed`, `.embedMany`, `baseURL`, `pricing`, `responseSchema`, `responseFormat`, `reasoning_effort`, `reasoningTokens`, `cachedTokens`, `gpt-image`, `dall-e`, `ai.image`, `pdf input`, `input_audio`, o-series / gpt-5 reasoning, `api: "responses"`, Responses API, reasoning with tools, prompt caching; "wire openai into a warlock agent", "configure gpt-4o", "use o3 / gpt-5 reasoning effort", "route through openrouter or azure openai", "openai embeddings with warlock", "generate images with gpt-image / dall-e", "send a pdf / audio to gpt-4o"; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: the ai.image verb surface — the `generate-images` topic of the `warlock-js-ai` skill; agent wiring — the `run-ai-agent` topic of the `warlock-js-ai` skill; adapter comparison — the `pick-ai-provider` topic of the `warlock-js-ai` skill; 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`.'
|
|
3
|
+
description: 'Wire @warlock.js/ai-openai — new OpenAISDK({apiKey, baseURL?, provider?, pricing?}) for OpenAI / Azure / OpenRouter, .model({name, vision?, reasoning?, structuredOutput?, responseFormat?, pdf?, audio?}) for ModelContract, .embedder({name, dimensions?}) for embeddings, .image({name, pricing?}) for gpt-image-*/dall-e-* image generation (via ai.image). PDF input maps to OpenAI file parts (opt-in pdf:true), audio input to input_audio (opt-in audio:true). Triggers: `providerOptions`, `OpenAICallOptions`, `countTokens`, `OpenAISDK`, `.model`, `.embedder`, `.image`, `.embed`, `.embedMany`, `baseURL`, `pricing`, `responseSchema`, `responseFormat`, `reasoning_effort`, `reasoningTokens`, `cachedTokens`, `cacheWriteTokens`, `inputIncludesCache`, `OPENAI_MODEL_PRICING`, `openAIPricingFor`, `ProviderOverloadedError`, `gpt-image`, `dall-e`, `ai.image`, `pdf input`, `input_audio`, o-series / gpt-5 reasoning, `api: "responses"`, Responses API, `ai.providerTool`, hosted `web_search`, `streamChunks`, `reasoning_content`, reasoning with tools, prompt caching; "wire openai into a warlock agent", "configure gpt-4o", "use o3 / gpt-5 reasoning effort", "route through openrouter or azure openai", "openai embeddings with warlock", "generate images with gpt-image / dall-e", "send a pdf / audio to gpt-4o"; typical import `import { OpenAISDK } from "@warlock.js/ai-openai"`. Skip: the ai.image verb surface — the `generate-images` topic of the `warlock-js-ai` skill; agent wiring — the `run-ai-agent` topic of the `warlock-js-ai` skill; adapter comparison — the `pick-ai-provider` topic of the `warlock-js-ai` skill; 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`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `@warlock.js/ai-openai`
|
|
@@ -31,23 +31,23 @@ const openrouter = new OpenAISDK({
|
|
|
31
31
|
## Producing a model
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
|
-
openai.model({ name: "gpt-4o-mini" })
|
|
35
|
-
openai.model({ name: "gpt-4o", temperature: 0.2 })
|
|
36
|
-
openai.model({ name: "fine-tuned-x", vision: true })
|
|
34
|
+
openai.model({ name: "gpt-4o-mini" }); // common case
|
|
35
|
+
openai.model({ name: "gpt-4o", temperature: 0.2 }); // sampling controls
|
|
36
|
+
openai.model({ name: "fine-tuned-x", vision: true }); // explicit capability override
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Returns a `ModelContract` you pass straight into `ai.agent({ model })`.
|
|
40
40
|
|
|
41
41
|
## Capabilities — what's auto-set
|
|
42
42
|
|
|
43
|
-
| Flag
|
|
44
|
-
|
|
|
45
|
-
| `structuredOutput` | `true`, unless `responseFormat` is set to `"json_object"` or `"text"` (loose modes) — then `false`.
|
|
46
|
-
| `vision`
|
|
47
|
-
| `reasoning`
|
|
48
|
-
| `promptCaching`
|
|
49
|
-
| `pdf`
|
|
50
|
-
| `audio`
|
|
43
|
+
| Flag | Default |
|
|
44
|
+
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| `structuredOutput` | `true`, unless `responseFormat` is set to `"json_object"` or `"text"` (loose modes) — then `false`. |
|
|
46
|
+
| `vision` | Inferred from model name. `true` for `gpt-4o*`, `gpt-4-turbo*`, `gpt-4.1*`, `o1*`, `o3*`, `chatgpt-4o*`; `false` otherwise. |
|
|
47
|
+
| `reasoning` | Inferred from model name. `true` for the o-series (`o1*`, `o3*`, `o4*`) and the `gpt-5*` family; `false` otherwise. Drives whether `reasoning_effort` is forwarded. |
|
|
48
|
+
| `promptCaching` | `true` for the direct OpenAI provider. It supports automatic cache hits plus explicit content-part write breakpoints; wrappers send only `prompt_cache_key`, and only where the endpoint documents it (xAI, Mistral). |
|
|
49
|
+
| `pdf` | `false` by default (opt-in `.model({ pdf: true })`). OpenAI accepts PDF `file` parts only on specific models (the `gpt-4o` family) — honest off until you set it. |
|
|
50
|
+
| `audio` | `false` by default (opt-in `.model({ audio: true })`). Only the `gpt-4o-audio-preview` family accepts `input_audio`. |
|
|
51
51
|
|
|
52
52
|
**Override `vision`, `structuredOutput`, `reasoning`, `pdf`, or `audio` explicitly** via `.model({ name, vision?, structuredOutput?, reasoning?, pdf?, audio? })` — an explicit value always wins over inference.
|
|
53
53
|
|
|
@@ -58,8 +58,8 @@ When the agent passes `responseSchema` (a JSON Schema object), the adapter conve
|
|
|
58
58
|
Some targets reject strict `json_schema` (older OpenAI models, OpenRouter routes, Ollama OpenAI-compat). Override the wire mode per model with `responseFormat`:
|
|
59
59
|
|
|
60
60
|
```ts
|
|
61
|
-
openai.model({ name: "legacy-model", responseFormat: "json_object" }) // valid JSON, no strict shape
|
|
62
|
-
openai.model({ name: "some-route",
|
|
61
|
+
openai.model({ name: "legacy-model", responseFormat: "json_object" }); // valid JSON, no strict shape
|
|
62
|
+
openai.model({ name: "some-route", responseFormat: "text" }); // no response_format on the wire
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
`"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.
|
|
@@ -82,7 +82,7 @@ PDF and audio reach the wire only when the model declares the matching capabilit
|
|
|
82
82
|
|
|
83
83
|
```ts
|
|
84
84
|
const gpt = openai.image({ name: "gpt-image-1", pricing: { input: 5, output: 40 } }); // token-metered
|
|
85
|
-
const dalle = openai.image({ name: "dall-e-3", pricing: { perImage: 0.04 } });
|
|
85
|
+
const dalle = openai.image({ name: "dall-e-3", pricing: { perImage: 0.04 } }); // per-image
|
|
86
86
|
|
|
87
87
|
const { data } = await ai.image({ model: gpt, prompt: "a red bicycle", size: "1024x1024" });
|
|
88
88
|
```
|
|
@@ -97,6 +97,20 @@ const { data } = await ai.image({ model: gpt, prompt: "a red bicycle", size: "10
|
|
|
97
97
|
|
|
98
98
|
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.
|
|
99
99
|
|
|
100
|
+
Opt-in chunks (`options.streamChunks`, set by `agent.stream()`): Chat yields `{ type: "tool-args", callId, name, delta }` per argument fragment and `{ type: "reasoning", delta }` for a compatible vendor's `delta.reasoning_content` (DeepSeek) / `delta.reasoning` (Groq); Responses yields `reasoning` for `response.reasoning_summary_text.delta` / `response.reasoning_text.delta` (summaries need `body: { reasoning: { summary: "auto" } }`) and `tool-args` for `response.function_call_arguments.delta`. No `usage` chunk (usage arrives at the end).
|
|
101
|
+
|
|
102
|
+
## Provider-defined tools
|
|
103
|
+
|
|
104
|
+
With `api: "responses"`, `ai.providerTool({ provider: "openai", definition })` appends hosted tools verbatim (`{ type: "web_search" }`, `file_search`, `code_interpreter`, `image_generation`, `mcp`). Hosted-call items are not replayed (`store: false`), and a reasoning item that led into one is dropped from the replay. Client-run built-ins (`shell_call`, `apply_patch_call`, `computer_call`, `custom_tool_call`, `local_shell_call`, `mcp_approval_request`) throw `InvalidRequestError`. On Chat Completions a provider tool is sent only when `provider` equals the model's provider label (e.g. Groq `{ type: "browser_search" }` with `provider: "groq"`).
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const model = openai.model({ name: "gpt-5.6", api: "responses" });
|
|
108
|
+
ai.agent({
|
|
109
|
+
model,
|
|
110
|
+
tools: [ai.providerTool({ provider: "openai", definition: { type: "web_search" } }), lookupOrder],
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
100
114
|
## Embeddings
|
|
101
115
|
|
|
102
116
|
```ts
|
|
@@ -117,8 +131,8 @@ openai.embedder({ name: "text-embedding-3-large", dimensions: 256 });
|
|
|
117
131
|
Reasoning models accept a discrete effort knob. The agent passes it through `ModelCallOptions.reasoning`:
|
|
118
132
|
|
|
119
133
|
```ts
|
|
120
|
-
const model = openai.model({ name: "o3-mini" });
|
|
121
|
-
await model.complete(messages, { reasoning: { effort: "high" } });
|
|
134
|
+
const model = openai.model({ name: "o3-mini" }); // reasoning auto-true
|
|
135
|
+
await model.complete(messages, { reasoning: { effort: "high" } }); // → reasoning_effort: "high"
|
|
122
136
|
```
|
|
123
137
|
|
|
124
138
|
- `reasoning.effort` (`"low" | "medium" | "high" | "none"`) maps verbatim to OpenAI's `reasoning_effort` request param.
|
|
@@ -138,8 +152,8 @@ reasoning_effort to 'none'.
|
|
|
138
152
|
Since there is no working alternative to `"none"` in that state, the adapter defaults to it automatically: whenever a call is made to a reasoning-capable model WITH `tools` AND the caller supplied no `reasoning.effort` at all, `reasoning_effort: "none"` is emitted for you — no opt-in required.
|
|
139
153
|
|
|
140
154
|
```ts
|
|
141
|
-
const model = openai.model({ name: "gpt-5-mini" });
|
|
142
|
-
await model.complete(messages, { tools });
|
|
155
|
+
const model = openai.model({ name: "gpt-5-mini" }); // reasoning auto-true
|
|
156
|
+
await model.complete(messages, { tools }); // → reasoning_effort: "none", automatically
|
|
143
157
|
```
|
|
144
158
|
|
|
145
159
|
An explicit `reasoning.effort` always overrides the default, in either direction — pass it yourself if you want a different value (and accept that tools may then be rejected, per the error above, since Chat Completions still can't do reasoning + tools together):
|
|
@@ -180,15 +194,18 @@ await model.complete(messages, { tools, reasoning: { effort: "high" } }); // rea
|
|
|
180
194
|
|
|
181
195
|
`usage` on the result carries the neutral channel breakdown, populated from OpenAI's `usage` block:
|
|
182
196
|
|
|
183
|
-
| `Usage` field
|
|
184
|
-
|
|
|
185
|
-
| `input` / `output` / `total` | `prompt_tokens` / `completion_tokens` / `total_tokens` | always present (zeroed if OpenAI omits the block).
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
197
|
+
| `Usage` field | OpenAI source | Notes |
|
|
198
|
+
| ---------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
|
|
199
|
+
| `input` / `output` / `total` | `prompt_tokens` / `completion_tokens` / `total_tokens` | always present (zeroed if OpenAI omits the block). |
|
|
200
|
+
| `inputIncludesCache` | — (always `true`) | `prompt_tokens` already contains the cached and cache-written tokens; cost bills them out of it once. |
|
|
201
|
+
| `cachedTokens` | `prompt_tokens_details.cached_tokens` | subset of `input` served from OpenAI's automatic prompt cache. Emitted only when > 0. |
|
|
202
|
+
| `cacheWriteTokens` | `prompt_tokens_details.cache_write_tokens` | subset of `input` written to the cache, on the models that charge for writes. Emitted only when > 0. |
|
|
203
|
+
| `reasoningTokens` | `completion_tokens_details.reasoning_tokens` | hidden reasoning channel on o-series / gpt-5 (already counted within `output`). Emitted only when > 0. |
|
|
204
|
+
| `cost` | computed | per-channel USD when the model is priced (see [Pricing](#pricing--built-in-list-prices-and-registry)). |
|
|
188
205
|
|
|
189
|
-
|
|
206
|
+
The detail counters are omitted (not set to `0`) when the provider reports zero, so non-reasoning / uncached calls keep the lean `{ input, output, total, inputIncludesCache }` shape. Reported identically on `complete()`, the streaming `done` event and the Responses API (`input_tokens_details.cached_tokens` / `cache_write_tokens`). The OpenAI-compatible wrappers (DeepSeek, xAI, Groq, Mistral) report the same fields and the same inclusive semantics.
|
|
190
207
|
|
|
191
|
-
### Explicit prompt caching
|
|
208
|
+
### Explicit prompt caching
|
|
192
209
|
|
|
193
210
|
Chat Completions exposes OpenAI-specific cache controls on current SDK types. Configure a stable cache bucket and (when needed) its maximum retention policy per model:
|
|
194
211
|
|
|
@@ -202,39 +219,113 @@ const model = openai.model({
|
|
|
202
219
|
|
|
203
220
|
`promptCacheKey` improves matching for related requests. `promptCacheRetention` is OpenAI's maximum policy (`"in_memory" | "24h"`). `cacheControl.breakpoints` maps to `prompt_cache_breakpoint: { mode: "explicit" }` on the final user content parts (up to OpenAI's four-request limit); use it when you need to pin a reusable prefix rather than rely only on OpenAI's implicit breakpoint.
|
|
204
221
|
|
|
205
|
-
|
|
222
|
+
Which fields reach the wire depends on the SDK provider label:
|
|
223
|
+
|
|
224
|
+
| Provider label | `prompt_cache_key` | `prompt_cache_retention` / explicit breakpoints | Why |
|
|
225
|
+
| ---------------------- | ------------------ | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
226
|
+
| `"openai"` | sent | sent | https://developers.openai.com/api/docs/guides/prompt-caching |
|
|
227
|
+
| `"xai"` | sent | — | xAI Chat Completions accepts `prompt_cache_key` (routed as `x-grok-conv-id`) — https://docs.x.ai/developers/advanced-api-usage/prompt-caching/maximizing-cache-hits |
|
|
228
|
+
| `"mistral"` | sent | — | https://docs.mistral.ai/studio/conversations/advanced/prompt-caching |
|
|
229
|
+
| `"groq"`, `"deepseek"` | — | — | both cache automatically and document no request hint |
|
|
230
|
+
| any custom label | — | — | an unknown endpoint may reject the fields |
|
|
231
|
+
|
|
232
|
+
(Checked 2026-10-17.) `XaiSDK` and `MistralSDK` keep their default labels unless you pass `provider`, so `xai.model({ name, promptCacheKey })` works out of the box. Read-side `usage.cachedTokens` accounting remains available whenever the upstream reports it.
|
|
206
233
|
|
|
207
234
|
## Token counting
|
|
208
235
|
|
|
209
236
|
```ts
|
|
210
|
-
await openai.count("some text")
|
|
237
|
+
await openai.count("some text"); // approximate (heuristic, not tiktoken)
|
|
211
238
|
```
|
|
212
239
|
|
|
213
240
|
Good enough for budgeting; not for billing.
|
|
214
241
|
|
|
215
|
-
## Pricing —
|
|
242
|
+
## Pricing — built-in list prices and registry
|
|
216
243
|
|
|
217
|
-
|
|
244
|
+
All rates are **USD per 1,000,000 tokens** (`ModelPricing`: `input`, `output`, optional `cachedInput`, `cacheWrite`, `cacheWrite1h`, `reasoning`). Resolution at `model()` time: per-model `pricing` (`openai.model({ name, pricing })`) > SDK `pricing` registry > built-in `OPENAI_MODEL_PRICING` (only when the SDK provider label is `"openai"`, exact model id) > `undefined` (no cost computed).
|
|
218
245
|
|
|
219
246
|
```ts
|
|
247
|
+
import { ai, totalCost } from "@warlock.js/ai";
|
|
248
|
+
import { OPENAI_MODEL_PRICING, OpenAISDK, openAIPricingFor } from "@warlock.js/ai-openai";
|
|
249
|
+
|
|
220
250
|
const openai = new OpenAISDK({
|
|
221
|
-
apiKey,
|
|
251
|
+
apiKey: process.env.OPENAI_API_KEY ?? "",
|
|
222
252
|
pricing: {
|
|
223
|
-
//
|
|
224
|
-
"gpt-
|
|
225
|
-
"gpt-4o": { input: 2.5, output: 10, cachedInput: 1.25 },
|
|
253
|
+
// Prompt-length-tiered models have no built-in rate — price them yourself.
|
|
254
|
+
"gpt-5.5": { input: 5, output: 30, cachedInput: 0.5 },
|
|
226
255
|
},
|
|
227
256
|
});
|
|
228
257
|
|
|
258
|
+
openai.model({ name: "gpt-4o-mini" }).pricing; // built-in: { input: 0.15, output: 0.6, cachedInput: 0.075 }
|
|
259
|
+
openAIPricingFor("gpt-4o-2024-05-13"); // undefined — dated snapshots can carry other prices
|
|
260
|
+
Object.keys(OPENAI_MODEL_PRICING); // the priced ids
|
|
261
|
+
|
|
229
262
|
const { usage } = await ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) }).execute("hi");
|
|
230
|
-
usage.cost
|
|
263
|
+
console.log(usage.cost, totalCost(usage.cost)); // per-channel USD breakdown of THIS run, and its sum
|
|
264
|
+
|
|
265
|
+
// A direct call is costed too — no agent needed.
|
|
266
|
+
const side = await openai
|
|
267
|
+
.model({ name: "gpt-4.1-mini" })
|
|
268
|
+
.complete([{ role: "user", content: "Classify: …" }]);
|
|
269
|
+
console.log(totalCost(side.usage.cost));
|
|
231
270
|
```
|
|
232
271
|
|
|
233
|
-
|
|
272
|
+
- **Source.** `OPENAI_MODEL_PRICING` comes from https://developers.openai.com/api/docs/pricing (the target of platform.openai.com/docs/pricing), Standard tier, fetched 2026-10-17. It covers the flat-priced models: `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.2`, `gpt-5.1`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-4.1` (+ `-mini` / `-nano`), `gpt-4o`, `gpt-4o-mini`, `o3`, `o4-mini`, `o3-mini`, `o1`. None of them charges for cache writes.
|
|
273
|
+
- **Deliberately unpriced:** models priced by prompt length (a higher rate above 272K input tokens — the gpt-6, gpt-5.6, gpt-5.5 and gpt-5.4 families and the `-pro` models), dated snapshot ids, promotional / Batch / Flex / Priority pricing, and the regional data-residency uplift. Wrapper SDKs (`provider` other than `"openai"`) never fall back to this table.
|
|
234
274
|
|
|
235
275
|
## Errors
|
|
236
276
|
|
|
237
|
-
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 the `handle-ai-errors` topic of the `warlock-js-ai` skill.
|
|
277
|
+
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 the `handle-ai-errors` topic of the `warlock-js-ai` skill. HTTP 503 and code `server_is_overloaded` (https://developers.openai.com/api/docs/guides/error-codes) map to `ProviderOverloadedError` (`code: "PROVIDER_OVERLOADED"`, `retryable: true`, `retryAfter` from `retry-after`); the compatible wrappers use 503 for the same condition (DeepSeek documents it as "Server Overloaded"). Every wrapped error carries `status`, `retryable` and `billed` — `false` for 401 / 402 / 403, otherwise `"unknown"`, because OpenAI publishes no rule for failed requests.
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
import { ai, ProviderOverloadedError } from "@warlock.js/ai";
|
|
281
|
+
import { OpenAISDK } from "@warlock.js/ai-openai";
|
|
282
|
+
|
|
283
|
+
const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY ?? "" });
|
|
284
|
+
const result = await ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) }).execute("hi");
|
|
285
|
+
|
|
286
|
+
if (result.error instanceof ProviderOverloadedError) {
|
|
287
|
+
console.log(result.error.status, result.error.retryAfter); // back off, or fall back to another model
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Provider body options and input-token counts
|
|
292
|
+
|
|
293
|
+
Native body fields merge after neutral options. When both values are plain
|
|
294
|
+
objects, their immediate keys merge and caller-provided keys win; untouched
|
|
295
|
+
neutral fields remain. Arrays, scalars and `null` replace the field. This merge
|
|
296
|
+
is one level deep: a nested object explicitly supplied by the caller replaces
|
|
297
|
+
that nested value. When both objects have string `type` discriminators and the
|
|
298
|
+
caller changes the type, the whole object is replaced. For example, overriding
|
|
299
|
+
`response_format` from `json_schema` to `json_object` removes the old
|
|
300
|
+
`json_schema` fields; switching `tool_choice` variants removes old variant fields
|
|
301
|
+
as well. The rule also applies to OpenAI-compatible adapters using this helper.
|
|
302
|
+
|
|
303
|
+
`providerOptions.openai.body` adds native request fields to Chat Completions or
|
|
304
|
+
Responses, merged after the neutral options. For example:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
const options = { providerOptions: { openai: { body: { service_tier: "default" } } } };
|
|
308
|
+
await model.complete(messages, options);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The key stays `openai` for compatible gateways with a custom provider label.
|
|
312
|
+
Other provider keys are ignored. `OpenAICallOptions` is exported. The body cannot
|
|
313
|
+
replace `model`, `messages`, `input`, `instructions`, `tools`, `stream`,
|
|
314
|
+
`stream_options`, `previous_response_id`, `conversation`, `store`, `include`, `functions`, `function_call`, `prompt` or `background`;
|
|
315
|
+
those fields protect the adapter's conversation, tools and stateless replay.
|
|
316
|
+
|
|
317
|
+
Models using `api: "responses"` expose native `countTokens(messages, options?)`
|
|
318
|
+
through `responses.inputTokens.count`: the same input, system instructions,
|
|
319
|
+
tools, reasoning, text format, tool choice, parallel-tool setting and truncation
|
|
320
|
+
used for generation are counted. The endpoint supports only input-shaping fields;
|
|
321
|
+
output caps and generation-only options are omitted. The Chat Completions model
|
|
322
|
+
has no `countTokens` method. `sdk.count(text)` remains a separate offline estimate.
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
if (model.countTokens) {
|
|
326
|
+
const inputTokens = await model.countTokens(messages, options);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
238
329
|
|
|
239
330
|
## When NOT to use this skill
|
|
240
331
|
|