@warlock.js/ai-openai 5.30.0 → 5.32.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 +42 -1
- package/README.md +3 -3
- package/cjs/index.cjs +715 -51
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +13 -13
- package/esm/config.type.d.mts.map +1 -1
- package/esm/embedder.d.mts +1 -3
- package/esm/embedder.d.mts.map +1 -1
- package/esm/embedder.mjs.map +1 -1
- package/esm/image.d.mts +1 -3
- package/esm/image.d.mts.map +1 -1
- package/esm/index.d.mts +4 -1
- package/esm/index.mjs +3 -1
- package/esm/kimi-tools.d.mts +69 -0
- package/esm/kimi-tools.d.mts.map +1 -0
- package/esm/kimi-tools.mjs +256 -0
- package/esm/kimi-tools.mjs.map +1 -0
- package/esm/known-pricing.d.mts +39 -0
- package/esm/known-pricing.d.mts.map +1 -0
- package/esm/known-pricing.mjs +121 -0
- package/esm/known-pricing.mjs.map +1 -0
- package/esm/model.mjs +82 -23
- package/esm/model.mjs.map +1 -1
- package/esm/provider-options.d.mts +13 -0
- package/esm/provider-options.d.mts.map +1 -0
- package/esm/provider-options.mjs +56 -0
- package/esm/provider-options.mjs.map +1 -0
- package/esm/responses-model.mjs +125 -19
- package/esm/responses-model.mjs.map +1 -1
- package/esm/sdk.d.mts +4 -5
- package/esm/sdk.d.mts.map +1 -1
- package/esm/sdk.mjs +5 -3
- package/esm/sdk.mjs.map +1 -1
- package/esm/speech.d.mts +1 -3
- package/esm/speech.d.mts.map +1 -1
- package/esm/transcription.d.mts +1 -3
- package/esm/transcription.d.mts.map +1 -1
- package/esm/transcription.mjs.map +1 -1
- package/esm/utils/build-usage.mjs +14 -2
- package/esm/utils/build-usage.mjs.map +1 -1
- package/esm/utils/hosted-output.mjs +22 -0
- package/esm/utils/hosted-output.mjs.map +1 -0
- package/esm/utils/index.mjs +2 -2
- package/esm/utils/map-responses-status.mjs.map +1 -1
- package/esm/utils/prompt-cache-params.mjs +25 -6
- package/esm/utils/prompt-cache-params.mjs.map +1 -1
- package/esm/utils/reasoning-replay.mjs +23 -1
- 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.map +1 -1
- package/esm/utils/to-openai-tools.mjs.map +1 -1
- package/esm/utils/wrap-openai-error.mjs +6 -1
- package/esm/utils/wrap-openai-error.mjs.map +1 -1
- package/llms-full.txt +235 -37
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/skills/setup-openai/SKILL.md +235 -37
package/llms-full.txt
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
name: setup-openai
|
|
11
|
-
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`.'
|
|
11
|
+
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`, `kimiSearchTool`, `kimiFetchTool`, `KIMI_SERVER_TOOL_PRICING`, `.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`.'
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# `@warlock.js/ai-openai`
|
|
@@ -39,23 +39,23 @@ const openrouter = new OpenAISDK({
|
|
|
39
39
|
## Producing a model
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
|
-
openai.model({ name: "gpt-4o-mini" })
|
|
43
|
-
openai.model({ name: "gpt-4o", temperature: 0.2 })
|
|
44
|
-
openai.model({ name: "fine-tuned-x", vision: true })
|
|
42
|
+
openai.model({ name: "gpt-4o-mini" }); // common case
|
|
43
|
+
openai.model({ name: "gpt-4o", temperature: 0.2 }); // sampling controls
|
|
44
|
+
openai.model({ name: "fine-tuned-x", vision: true }); // explicit capability override
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
Returns a `ModelContract` you pass straight into `ai.agent({ model })`.
|
|
48
48
|
|
|
49
49
|
## Capabilities — what's auto-set
|
|
50
50
|
|
|
51
|
-
| Flag
|
|
52
|
-
|
|
|
53
|
-
| `structuredOutput` | `true`, unless `responseFormat` is set to `"json_object"` or `"text"` (loose modes) — then `false`.
|
|
54
|
-
| `vision`
|
|
55
|
-
| `reasoning`
|
|
56
|
-
| `promptCaching`
|
|
57
|
-
| `pdf`
|
|
58
|
-
| `audio`
|
|
51
|
+
| Flag | Default |
|
|
52
|
+
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| `structuredOutput` | `true`, unless `responseFormat` is set to `"json_object"` or `"text"` (loose modes) — then `false`. |
|
|
54
|
+
| `vision` | Inferred from model name. `true` for `gpt-4o*`, `gpt-4-turbo*`, `gpt-4.1*`, `o1*`, `o3*`, `chatgpt-4o*`; `false` otherwise. |
|
|
55
|
+
| `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. |
|
|
56
|
+
| `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). |
|
|
57
|
+
| `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. |
|
|
58
|
+
| `audio` | `false` by default (opt-in `.model({ audio: true })`). Only the `gpt-4o-audio-preview` family accepts `input_audio`. |
|
|
59
59
|
|
|
60
60
|
**Override `vision`, `structuredOutput`, `reasoning`, `pdf`, or `audio` explicitly** via `.model({ name, vision?, structuredOutput?, reasoning?, pdf?, audio? })` — an explicit value always wins over inference.
|
|
61
61
|
|
|
@@ -66,8 +66,8 @@ When the agent passes `responseSchema` (a JSON Schema object), the adapter conve
|
|
|
66
66
|
Some targets reject strict `json_schema` (older OpenAI models, OpenRouter routes, Ollama OpenAI-compat). Override the wire mode per model with `responseFormat`:
|
|
67
67
|
|
|
68
68
|
```ts
|
|
69
|
-
openai.model({ name: "legacy-model", responseFormat: "json_object" }) // valid JSON, no strict shape
|
|
70
|
-
openai.model({ name: "some-route",
|
|
69
|
+
openai.model({ name: "legacy-model", responseFormat: "json_object" }); // valid JSON, no strict shape
|
|
70
|
+
openai.model({ name: "some-route", responseFormat: "text" }); // no response_format on the wire
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
`"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.
|
|
@@ -90,7 +90,7 @@ PDF and audio reach the wire only when the model declares the matching capabilit
|
|
|
90
90
|
|
|
91
91
|
```ts
|
|
92
92
|
const gpt = openai.image({ name: "gpt-image-1", pricing: { input: 5, output: 40 } }); // token-metered
|
|
93
|
-
const dalle = openai.image({ name: "dall-e-3", pricing: { perImage: 0.04 } });
|
|
93
|
+
const dalle = openai.image({ name: "dall-e-3", pricing: { perImage: 0.04 } }); // per-image
|
|
94
94
|
|
|
95
95
|
const { data } = await ai.image({ model: gpt, prompt: "a red bicycle", size: "1024x1024" });
|
|
96
96
|
```
|
|
@@ -105,6 +105,20 @@ const { data } = await ai.image({ model: gpt, prompt: "a red bicycle", size: "10
|
|
|
105
105
|
|
|
106
106
|
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.
|
|
107
107
|
|
|
108
|
+
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).
|
|
109
|
+
|
|
110
|
+
## Provider-defined tools
|
|
111
|
+
|
|
112
|
+
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"`).
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const model = openai.model({ name: "gpt-5.6", api: "responses" });
|
|
116
|
+
ai.agent({
|
|
117
|
+
model,
|
|
118
|
+
tools: [ai.providerTool({ provider: "openai", definition: { type: "web_search" } }), lookupOrder],
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
108
122
|
## Embeddings
|
|
109
123
|
|
|
110
124
|
```ts
|
|
@@ -125,8 +139,8 @@ openai.embedder({ name: "text-embedding-3-large", dimensions: 256 });
|
|
|
125
139
|
Reasoning models accept a discrete effort knob. The agent passes it through `ModelCallOptions.reasoning`:
|
|
126
140
|
|
|
127
141
|
```ts
|
|
128
|
-
const model = openai.model({ name: "o3-mini" });
|
|
129
|
-
await model.complete(messages, { reasoning: { effort: "high" } });
|
|
142
|
+
const model = openai.model({ name: "o3-mini" }); // reasoning auto-true
|
|
143
|
+
await model.complete(messages, { reasoning: { effort: "high" } }); // → reasoning_effort: "high"
|
|
130
144
|
```
|
|
131
145
|
|
|
132
146
|
- `reasoning.effort` (`"low" | "medium" | "high" | "none"`) maps verbatim to OpenAI's `reasoning_effort` request param.
|
|
@@ -146,8 +160,8 @@ reasoning_effort to 'none'.
|
|
|
146
160
|
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.
|
|
147
161
|
|
|
148
162
|
```ts
|
|
149
|
-
const model = openai.model({ name: "gpt-5-mini" });
|
|
150
|
-
await model.complete(messages, { tools });
|
|
163
|
+
const model = openai.model({ name: "gpt-5-mini" }); // reasoning auto-true
|
|
164
|
+
await model.complete(messages, { tools }); // → reasoning_effort: "none", automatically
|
|
151
165
|
```
|
|
152
166
|
|
|
153
167
|
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):
|
|
@@ -188,15 +202,18 @@ await model.complete(messages, { tools, reasoning: { effort: "high" } }); // rea
|
|
|
188
202
|
|
|
189
203
|
`usage` on the result carries the neutral channel breakdown, populated from OpenAI's `usage` block:
|
|
190
204
|
|
|
191
|
-
| `Usage` field
|
|
192
|
-
|
|
|
193
|
-
| `input` / `output` / `total` | `prompt_tokens` / `completion_tokens` / `total_tokens` | always present (zeroed if OpenAI omits the block).
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
205
|
+
| `Usage` field | OpenAI source | Notes |
|
|
206
|
+
| ---------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
|
|
207
|
+
| `input` / `output` / `total` | `prompt_tokens` / `completion_tokens` / `total_tokens` | always present (zeroed if OpenAI omits the block). |
|
|
208
|
+
| `inputIncludesCache` | — (always `true`) | `prompt_tokens` already contains the cached and cache-written tokens; cost bills them out of it once. |
|
|
209
|
+
| `cachedTokens` | `prompt_tokens_details.cached_tokens` | subset of `input` served from OpenAI's automatic prompt cache. Emitted only when > 0. |
|
|
210
|
+
| `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. |
|
|
211
|
+
| `reasoningTokens` | `completion_tokens_details.reasoning_tokens` | hidden reasoning channel on o-series / gpt-5 (already counted within `output`). Emitted only when > 0. |
|
|
212
|
+
| `cost` | computed | per-channel USD when the model is priced (see [Pricing](#pricing--built-in-list-prices-and-registry)). |
|
|
196
213
|
|
|
197
|
-
|
|
214
|
+
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.
|
|
198
215
|
|
|
199
|
-
### Explicit prompt caching
|
|
216
|
+
### Explicit prompt caching
|
|
200
217
|
|
|
201
218
|
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:
|
|
202
219
|
|
|
@@ -210,39 +227,120 @@ const model = openai.model({
|
|
|
210
227
|
|
|
211
228
|
`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.
|
|
212
229
|
|
|
213
|
-
|
|
230
|
+
Which fields reach the wire depends on the SDK provider label:
|
|
231
|
+
|
|
232
|
+
| Provider label | `prompt_cache_key` | `prompt_cache_retention` / explicit breakpoints | Why |
|
|
233
|
+
| ---------------------- | ------------------ | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
234
|
+
| `"openai"` | sent | sent | https://developers.openai.com/api/docs/guides/prompt-caching |
|
|
235
|
+
| `"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 |
|
|
236
|
+
| `"mistral"` | sent | — | https://docs.mistral.ai/studio/conversations/advanced/prompt-caching |
|
|
237
|
+
| `"groq"`, `"deepseek"` | — | — | both cache automatically and document no request hint |
|
|
238
|
+
| any custom label | — | — | an unknown endpoint may reject the fields |
|
|
239
|
+
|
|
240
|
+
(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.
|
|
214
241
|
|
|
215
242
|
## Token counting
|
|
216
243
|
|
|
217
244
|
```ts
|
|
218
|
-
await openai.count("some text")
|
|
245
|
+
await openai.count("some text"); // approximate (heuristic, not tiktoken)
|
|
219
246
|
```
|
|
220
247
|
|
|
221
248
|
Good enough for budgeting; not for billing.
|
|
222
249
|
|
|
223
|
-
## Pricing —
|
|
250
|
+
## Pricing — built-in list prices and registry
|
|
224
251
|
|
|
225
|
-
|
|
252
|
+
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).
|
|
226
253
|
|
|
227
254
|
```ts
|
|
255
|
+
import { ai, totalCost } from "@warlock.js/ai";
|
|
256
|
+
import { OPENAI_MODEL_PRICING, OpenAISDK, openAIPricingFor } from "@warlock.js/ai-openai";
|
|
257
|
+
|
|
228
258
|
const openai = new OpenAISDK({
|
|
229
|
-
apiKey,
|
|
259
|
+
apiKey: process.env.OPENAI_API_KEY ?? "",
|
|
230
260
|
pricing: {
|
|
231
|
-
//
|
|
232
|
-
"gpt-
|
|
233
|
-
"gpt-4o": { input: 2.5, output: 10, cachedInput: 1.25 },
|
|
261
|
+
// Prompt-length-tiered models have no built-in rate — price them yourself.
|
|
262
|
+
"gpt-5.5": { input: 5, output: 30, cachedInput: 0.5 },
|
|
234
263
|
},
|
|
235
264
|
});
|
|
236
265
|
|
|
266
|
+
openai.model({ name: "gpt-4o-mini" }).pricing; // built-in: { input: 0.15, output: 0.6, cachedInput: 0.075 }
|
|
267
|
+
openAIPricingFor("gpt-4o-2024-05-13"); // undefined — dated snapshots can carry other prices
|
|
268
|
+
Object.keys(OPENAI_MODEL_PRICING); // the priced ids
|
|
269
|
+
|
|
237
270
|
const { usage } = await ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) }).execute("hi");
|
|
238
|
-
usage.cost
|
|
271
|
+
console.log(usage.cost, totalCost(usage.cost)); // per-channel USD breakdown of THIS run, and its sum
|
|
272
|
+
|
|
273
|
+
// A direct call is costed too — no agent needed.
|
|
274
|
+
const side = await openai
|
|
275
|
+
.model({ name: "gpt-4.1-mini" })
|
|
276
|
+
.complete([{ role: "user", content: "Classify: …" }]);
|
|
277
|
+
console.log(totalCost(side.usage.cost));
|
|
239
278
|
```
|
|
240
279
|
|
|
241
|
-
|
|
280
|
+
- **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.
|
|
281
|
+
- **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.
|
|
242
282
|
|
|
243
283
|
## Errors
|
|
244
284
|
|
|
245
|
-
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.
|
|
285
|
+
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.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { ai, ProviderOverloadedError } from "@warlock.js/ai";
|
|
289
|
+
import { OpenAISDK } from "@warlock.js/ai-openai";
|
|
290
|
+
|
|
291
|
+
const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY ?? "" });
|
|
292
|
+
const result = await ai.agent({ model: openai.model({ name: "gpt-4o-mini" }) }).execute("hi");
|
|
293
|
+
|
|
294
|
+
if (result.error instanceof ProviderOverloadedError) {
|
|
295
|
+
console.log(result.error.status, result.error.retryAfter); // back off, or fall back to another model
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Provider body options and input-token counts
|
|
300
|
+
|
|
301
|
+
Native body fields merge after neutral options. When both values are plain
|
|
302
|
+
objects, their immediate keys merge and caller-provided keys win; untouched
|
|
303
|
+
neutral fields remain. Arrays, scalars and `null` replace the field. This merge
|
|
304
|
+
is one level deep: a nested object explicitly supplied by the caller replaces
|
|
305
|
+
that nested value. When both objects have string `type` discriminators and the
|
|
306
|
+
caller changes the type, the whole object is replaced. For example, overriding
|
|
307
|
+
`response_format` from `json_schema` to `json_object` removes the old
|
|
308
|
+
`json_schema` fields; switching `tool_choice` variants removes old variant fields
|
|
309
|
+
as well. The rule also applies to OpenAI-compatible adapters using this helper.
|
|
310
|
+
|
|
311
|
+
`providerOptions.openai.body` adds native request fields to Chat Completions or
|
|
312
|
+
Responses, merged after the neutral options. For example:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
const options = { providerOptions: { openai: { body: { service_tier: "default" } } } };
|
|
316
|
+
await model.complete(messages, options);
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The key stays `openai` for compatible gateways with a custom provider label.
|
|
320
|
+
Other provider keys are ignored. `OpenAICallOptions` is exported. The body cannot
|
|
321
|
+
replace `model`, `messages`, `input`, `instructions`, `tools`, `stream`,
|
|
322
|
+
`stream_options`, `previous_response_id`, `conversation`, `store`, `include`, `functions`, `function_call`, `prompt` or `background`;
|
|
323
|
+
those fields protect the adapter's conversation, tools and stateless replay.
|
|
324
|
+
|
|
325
|
+
Models using `api: "responses"` expose native `countTokens(messages, options?)`
|
|
326
|
+
through `responses.inputTokens.count`: the same input, system instructions,
|
|
327
|
+
tools, reasoning, text format, tool choice, parallel-tool setting and truncation
|
|
328
|
+
used for generation are counted. The endpoint supports only input-shaping fields;
|
|
329
|
+
output caps and generation-only options are omitted. The Chat Completions model
|
|
330
|
+
has no `countTokens` method. `sdk.count(text)` remains a separate offline estimate.
|
|
331
|
+
|
|
332
|
+
For budget preflight, `countInputTokens(model, messages, { tools })` from
|
|
333
|
+
`@warlock.js/ai` returns `{ tokens, source }`. It estimates only when native
|
|
334
|
+
counting is absent or explicitly throws `InvalidRequestError` with
|
|
335
|
+
`reason: "count-unsupported"`; this adapter preserves that signal. System and
|
|
336
|
+
tools are supported natively. Ordinary validation, auth, network and rate-limit
|
|
337
|
+
errors propagate. See the cost/budget guide for reserve mode.
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
if (model.countTokens) {
|
|
341
|
+
const inputTokens = await model.countTokens(messages, options);
|
|
342
|
+
}
|
|
343
|
+
```
|
|
246
344
|
|
|
247
345
|
## When NOT to use this skill
|
|
248
346
|
|
|
@@ -255,4 +353,104 @@ Raw OpenAI SDK errors are wrapped into the typed `@warlock.js/ai` `AIError` hier
|
|
|
255
353
|
- The `embed-text` topic of the `warlock-js-ai` skill — embedder usage
|
|
256
354
|
- The `pick-ai-provider` topic of the `warlock-js-ai` skill — adapter comparison
|
|
257
355
|
|
|
356
|
+
## Hosted-search metering and metadata
|
|
357
|
+
|
|
358
|
+
On Responses, each structured `web_search_call` output item contributes one
|
|
359
|
+
`usage.serverToolUse.webSearchRequests`. Streaming item events and terminal
|
|
360
|
+
output are reconciled as snapshots, so the same call is counted once.
|
|
361
|
+
`response.providerMetadata.openai.output` (also on streaming `done`) retains
|
|
362
|
+
hosted output items with their action and sources. The adapter automatically
|
|
363
|
+
requests `web_search_call.action.sources` when web-search tools are supplied.
|
|
364
|
+
Agent reports preserve this metadata on each trip; it is not replayed or
|
|
365
|
+
passed to function dispatch.
|
|
366
|
+
|
|
367
|
+
Explicitly set `pricing.serverTools: OPENAI_SERVER_TOOL_PRICING` (exported by
|
|
368
|
+
this package) for standard `web_search`: $0.01 per tool call, plus search-content
|
|
369
|
+
tokens at model rates. Non-reasoning `web_search_preview` has a different fee
|
|
370
|
+
and needs an explicit override. These prices were verified on 2026-10-18 from
|
|
371
|
+
[OpenAI pricing](https://developers.openai.com/api/docs/pricing#built-in-tools);
|
|
372
|
+
see the [official search output schema](https://developers.openai.com/api/docs/guides/tools-web-search)
|
|
373
|
+
and [cost tracking](https://warlock.js.org/v/latest/ai/recipes/cost-tracking/#hosted-tool-request-fees).
|
|
374
|
+
Chat Completions does not map hosted-search billing counters.
|
|
375
|
+
|
|
376
|
+
Qwen's `enableSearch` flag and Kimi's standalone `kimiSearchTool()` /
|
|
377
|
+
`kimiFetchTool()` have separate controls; Kimi's legacy builtin retires on
|
|
378
|
+
20 October 2026 and is unsupported; see [OpenAI-compatible native search](https://warlock.js.org/v/latest/ai/providers/openai-compatible/).
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
import { OPENAI_SERVER_TOOL_PRICING } from "@warlock.js/ai-openai";
|
|
382
|
+
const model = openai.model({
|
|
383
|
+
name: "gpt-4.1",
|
|
384
|
+
api: "responses",
|
|
385
|
+
pricing: { ...{ input: 2, output: 8 }, serverTools: OPENAI_SERVER_TOOL_PRICING },
|
|
386
|
+
});
|
|
387
|
+
const response = await model.complete(messages, {
|
|
388
|
+
providerTools: [{ provider: "openai", definition: { type: "web_search" } }],
|
|
389
|
+
});
|
|
390
|
+
console.log(response.usage.serverToolUse?.webSearchRequests);
|
|
391
|
+
console.log(response.providerMetadata);
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## Kimi first-party search and fetch
|
|
395
|
+
|
|
396
|
+
Use the same API key/base URL configuration for the model and client tools:
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
import { ai, createToolContext } from "@warlock.js/ai";
|
|
400
|
+
import { OpenAISDK, kimiSearchTool, kimiFetchTool } from "@warlock.js/ai-openai";
|
|
401
|
+
|
|
402
|
+
const config = {
|
|
403
|
+
apiKey: process.env.MOONSHOT_API_KEY ?? "",
|
|
404
|
+
baseURL: "https://api.moonshot.ai/v1",
|
|
405
|
+
};
|
|
406
|
+
const sdk = new OpenAISDK({ ...config, provider: "moonshot" });
|
|
407
|
+
const basic = kimiSearchTool({ ...config, includeContent: false });
|
|
408
|
+
const pro = kimiSearchTool({
|
|
409
|
+
...config,
|
|
410
|
+
mode: "pro",
|
|
411
|
+
sites: ["moonshot.ai"],
|
|
412
|
+
timeWindow: { start: "2026-01", end: "2026-10" },
|
|
413
|
+
limit: 5,
|
|
414
|
+
timeoutSeconds: 30,
|
|
415
|
+
});
|
|
416
|
+
const fetchPage = kimiFetchTool({ ...config, maxContentChars: 12_000 });
|
|
417
|
+
const assistant = ai.agent({ model: sdk.model({ name: "kimi-k3" }), tools: [pro, fetchPage] });
|
|
418
|
+
const result = await assistant.execute("Research prompt caching");
|
|
419
|
+
console.log(result.usage.serverToolUse, result.usage.cost?.serverTools);
|
|
420
|
+
|
|
421
|
+
const controller = new AbortController();
|
|
422
|
+
const found = await basic.invoke(
|
|
423
|
+
{ query: "Kimi prompt caching" },
|
|
424
|
+
createToolContext({ signal: controller.signal }),
|
|
425
|
+
);
|
|
426
|
+
console.log(found.data?.results, found.usage);
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Pass these functions directly as ordinary `tools`, without `ai.providerTool`.
|
|
430
|
+
They call `/v1/tools/search`, `/v1/tools/search_pro` or `/v1/tools/fetch` with
|
|
431
|
+
Bearer auth. Search arguments are `{ query }`; fetch arguments are `{ url }`.
|
|
432
|
+
Search results retain sources and optional full text (Basic) or scored chunks
|
|
433
|
+
(Pro); fetch returns Markdown. Text is bounded by `maxContentChars` (12,000 by
|
|
434
|
+
default), with `truncated` reporting trimming. `limit` is 1–20; timeoutSeconds
|
|
435
|
+
is 1–60; Pro accepts up to five sites and date filters. See
|
|
436
|
+
https://platform.kimi.ai/docs/api/tools-search,
|
|
437
|
+
https://platform.kimi.ai/docs/api/tools-search-pro and
|
|
438
|
+
https://platform.kimi.ai/docs/api/tools-fetch.
|
|
439
|
+
|
|
440
|
+
Use `invoke()` for the usage/report envelope. Per response, billable Basic,
|
|
441
|
+
Pro and Fetch units appear as `kimiSearchRequests`, `kimiSearchProRequests`
|
|
442
|
+
and `kimiFetchRequests`, alongside common search/fetch counters. Agents merge
|
|
443
|
+
these into their totals. `KIMI_SERVER_TOOL_PRICING` supplies the default rates
|
|
444
|
+
in `ModelPricing.serverTools` format; only namespaced keys are priced to avoid
|
|
445
|
+
double charges. Override `pricing` or pass `{}` to leave calls unpriced.
|
|
446
|
+
Official prices verified 2026-10-18: $0.002 / $0.003 / $0.002 respectively,
|
|
447
|
+
excluding taxes, only for HTTP 200 with nonempty search results/nonblank
|
|
448
|
+
Markdown: https://platform.kimi.ai/docs/pricing/websearch.
|
|
449
|
+
|
|
450
|
+
The legacy `$web_search` builtin retires on 20 October 2026 and is rejected.
|
|
451
|
+
Abort signals cancel transport; retries are disabled. `invoke().error` is a
|
|
452
|
+
`ToolExecutionError` whose `cause` is the mapped provider error (or
|
|
453
|
+
`AgentCancelledError` for caller cancellation). `execute()` returns data only
|
|
454
|
+
and throws mapped errors. Never infer fees from function calls or prose.
|
|
455
|
+
|
|
258
456
|
|
package/llms.txt
CHANGED
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
-
- [setup-openai](@warlock.js/ai-openai/setup-openai/SKILL.md): 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`.
|
|
9
|
+
- [setup-openai](@warlock.js/ai-openai/setup-openai/SKILL.md): 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`, `kimiSearchTool`, `kimiFetchTool`, `KIMI_SERVER_TOOL_PRICING`, `.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`.
|
package/package.json
CHANGED
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"openai": "^7.23.0",
|
|
18
|
-
"@warlock.js/logger": "5.
|
|
18
|
+
"@warlock.js/logger": "5.32.0"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"@warlock.js/ai": "5.
|
|
21
|
+
"@warlock.js/ai": "5.32.0"
|
|
22
22
|
},
|
|
23
|
-
"version": "5.
|
|
23
|
+
"version": "5.32.0",
|
|
24
24
|
"main": "./cjs/index.cjs",
|
|
25
25
|
"module": "./esm/index.mjs",
|
|
26
26
|
"types": "./esm/index.d.mts",
|