@warlock.js/ai-openai 5.25.0 → 5.27.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 +22 -0
- package/cjs/index.cjs +1043 -272
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +36 -2
- package/esm/config.type.d.mts.map +1 -1
- package/esm/known-reasoning-models.mjs +4 -2
- package/esm/known-reasoning-models.mjs.map +1 -1
- package/esm/known-vision-models.mjs +3 -0
- package/esm/known-vision-models.mjs.map +1 -1
- package/esm/model.mjs +52 -99
- package/esm/model.mjs.map +1 -1
- package/esm/responses-model.mjs +329 -0
- package/esm/responses-model.mjs.map +1 -0
- package/esm/sdk.d.mts +7 -0
- package/esm/sdk.d.mts.map +1 -1
- package/esm/sdk.mjs +21 -1
- package/esm/sdk.mjs.map +1 -1
- package/esm/utils/build-usage.mjs +23 -0
- package/esm/utils/build-usage.mjs.map +1 -0
- package/esm/utils/index.mjs +9 -1
- package/esm/utils/map-responses-status.mjs +54 -0
- package/esm/utils/map-responses-status.mjs.map +1 -0
- package/esm/utils/prompt-cache-params.mjs +17 -0
- package/esm/utils/prompt-cache-params.mjs.map +1 -0
- package/esm/utils/reasoning-effort.mjs +35 -0
- package/esm/utils/reasoning-effort.mjs.map +1 -0
- package/esm/utils/reasoning-replay.mjs +85 -0
- package/esm/utils/reasoning-replay.mjs.map +1 -0
- package/esm/utils/structured-output.mjs +84 -0
- package/esm/utils/structured-output.mjs.map +1 -0
- package/esm/utils/to-openai-messages.mjs +49 -5
- package/esm/utils/to-openai-messages.mjs.map +1 -1
- package/esm/utils/to-openai-tools.mjs +3 -3
- package/esm/utils/to-openai-tools.mjs.map +1 -1
- package/esm/utils/to-responses-input.mjs +112 -0
- package/esm/utils/to-responses-input.mjs.map +1 -0
- package/esm/utils/to-responses-tools.mjs +51 -0
- package/esm/utils/to-responses-tools.mjs.map +1 -0
- package/esm/utils/wrap-openai-error.mjs +7 -2
- package/esm/utils/wrap-openai-error.mjs.map +1 -1
- package/llms-full.txt +43 -4
- package/llms.txt +1 -1
- package/package.json +4 -4
- package/skills/setup-openai/SKILL.md +43 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrap-openai-error.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/wrap-openai-error.ts"],"sourcesContent":["import {\n AIError,\n ContentFilterError,\n ContextLengthExceededError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderRateLimitError,\n ProviderTimeoutError,\n QuotaExceededError,\n} from \"@warlock.js/ai\";\nimport OpenAI from \"openai\";\n\n/**\n * Raw-error fields the wrapper reads off an OpenAI SDK error.\n *\n * `APIError` exposes `status`, `code`, `message`, `type`, `headers` —\n * we duck-type because wrapped retries, proxied errors, and custom\n * error subclasses sometimes lose the `instanceof` relationship.\n */\ntype OpenAIErrorShape = {\n status?: number;\n code?: string | null;\n message?: string;\n type?: string | null;\n headers?: Record<string, string> | undefined;\n name?: string;\n};\n\n/**\n * Wrap any thrown value caught inside the OpenAI adapter into the\n * appropriate `@warlock.js/ai` `AIError` subclass.\n *\n * **Dispatch strategy.** Prefers `APIError.code` when present (stable\n * machine identifier across SDK versions), falls back to `status` when\n * `code` is missing (common with proxied deployments that strip the\n * field). Name-based detection (`APIConnectionTimeoutError`) catches\n * transport-layer errors that never produced an HTTP response.\n *\n * `AIError` instances are returned unchanged — callers can pass the\n * error through `try/catch/throw wrap(e)` pipelines without accidental\n * double-wrapping.\n *\n * @example\n * try {\n * return await this.client.chat.completions.create(...);\n * } catch (thrown) {\n * throw wrapOpenAIError(thrown);\n * }\n */\nexport function wrapOpenAIError(thrown: unknown): AIError {\n if (thrown instanceof AIError) {\n return thrown;\n }\n\n const shape = toShape(thrown);\n const context = buildContext(thrown, shape);\n const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));\n\n if (isTimeout(thrown, shape)) {\n return new ProviderTimeoutError(message, { cause: thrown, context });\n }\n\n if (shape.status === 401 || shape.code === \"invalid_api_key\") {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.code === \"insufficient_quota\") {\n return new QuotaExceededError(message, { cause: thrown, context });\n }\n\n if (shape.status === 429 || shape.code === \"rate_limit_exceeded\") {\n return new ProviderRateLimitError(message, {\n cause: thrown,\n context,\n retryAfter: parseRetryAfter(shape.headers),\n });\n }\n\n if (shape.code === \"context_length_exceeded\") {\n return new ContextLengthExceededError(message, { cause: thrown, context });\n }\n\n if (shape.code === \"content_filter\") {\n return new ContentFilterError(message, {\n cause: thrown,\n context,\n reason: message,\n });\n }\n\n if (typeof shape.status === \"number\" && shape.status >= 400 && shape.status < 500) {\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n return new ProviderError(message, { cause: thrown, context });\n}\n\n/**\n * Read the raw error shape without depending on `instanceof APIError`\n * — some consumers wrap the SDK, and proxies sometimes strip the\n * prototype chain. Duck-typing on the visible fields is resilient to\n * both.\n */\nfunction toShape(thrown: unknown): OpenAIErrorShape {\n if (thrown instanceof OpenAI.APIError) {\n return {\n status: thrown.status,\n code: thrown.code,\n message: thrown.message,\n type: thrown.type,\n headers: thrown.headers as Record<string, string> | undefined,\n name: thrown.name,\n };\n }\n\n if (typeof thrown === \"object\" && thrown !== null) {\n const raw = thrown as Record<string, unknown>;\n\n return {\n status: typeof raw.status === \"number\" ? raw.status : undefined,\n code: typeof raw.code === \"string\" ? raw.code : undefined,\n message: typeof raw.message === \"string\" ? raw.message : undefined,\n type: typeof raw.type === \"string\" ? raw.type : undefined,\n headers:\n typeof raw.headers === \"object\" && raw.headers !== null\n ? (raw.headers as Record<string, string>)\n : undefined,\n name: typeof raw.name === \"string\" ? raw.name : undefined,\n };\n }\n\n return {};\n}\n\n/**\n * Decide whether the thrown value represents a timeout. OpenAI's SDK\n * throws `APIConnectionTimeoutError` for transport-level timeouts, and\n * Node surfaces `ETIMEDOUT` / `ECONNABORTED` on the lower socket\n * layer. Either signal counts.\n */\nfunction isTimeout(thrown: unknown, shape: OpenAIErrorShape): boolean {\n if (thrown instanceof OpenAI.APIConnectionTimeoutError) {\n return true;\n }\n\n if (shape.name === \"APIConnectionTimeoutError\") {\n return true;\n }\n\n if (shape.code === \"ETIMEDOUT\" || shape.code === \"ECONNABORTED\") {\n return true;\n }\n\n return false;\n}\n\n/**\n * Attach the raw diagnostic fields to `error.context` so consumers\n * have everything the provider surfaced without each subclass having\n * to redeclare them. Never includes `cause` — that lives on\n * `error.cause`.\n */\nfunction buildContext(\n thrown: unknown,\n shape: OpenAIErrorShape,\n): Record<string, unknown> {\n const context: Record<string, unknown> = {};\n\n if (shape.status !== undefined) {\n context.status = shape.status;\n }\n\n if (shape.code) {\n context.code = shape.code;\n }\n\n if (shape.type) {\n context.type = shape.type;\n }\n\n const requestId = readRequestId(thrown);\n\n if (requestId) {\n context.requestId = requestId;\n }\n\n return context;\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"wrap-openai-error.mjs","names":[],"sources":["../../../../../../../ai-openai/src/utils/wrap-openai-error.ts"],"sourcesContent":["import {\n AIError,\n ContentFilterError,\n ContextLengthExceededError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderRateLimitError,\n ProviderTimeoutError,\n QuotaExceededError,\n} from \"@warlock.js/ai\";\nimport OpenAI from \"openai\";\n\n/**\n * Raw-error fields the wrapper reads off an OpenAI SDK error.\n *\n * `APIError` exposes `status`, `code`, `message`, `type`, `headers` —\n * we duck-type because wrapped retries, proxied errors, and custom\n * error subclasses sometimes lose the `instanceof` relationship.\n */\ntype OpenAIErrorShape = {\n status?: number;\n code?: string | null;\n message?: string;\n type?: string | null;\n /** The request parameter the provider rejected, e.g. `max_tokens`. */\n param?: string | null;\n headers?: Record<string, string> | undefined;\n name?: string;\n};\n\n/**\n * Wrap any thrown value caught inside the OpenAI adapter into the\n * appropriate `@warlock.js/ai` `AIError` subclass.\n *\n * **Dispatch strategy.** Prefers `APIError.code` when present (stable\n * machine identifier across SDK versions), falls back to `status` when\n * `code` is missing (common with proxied deployments that strip the\n * field). Name-based detection (`APIConnectionTimeoutError`) catches\n * transport-layer errors that never produced an HTTP response.\n *\n * `AIError` instances are returned unchanged — callers can pass the\n * error through `try/catch/throw wrap(e)` pipelines without accidental\n * double-wrapping.\n *\n * @example\n * try {\n * return await this.client.chat.completions.create(...);\n * } catch (thrown) {\n * throw wrapOpenAIError(thrown);\n * }\n */\nexport function wrapOpenAIError(thrown: unknown): AIError {\n if (thrown instanceof AIError) {\n return thrown;\n }\n\n const shape = toShape(thrown);\n const context = buildContext(thrown, shape);\n const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));\n\n if (isTimeout(thrown, shape)) {\n return new ProviderTimeoutError(message, { cause: thrown, context });\n }\n\n if (shape.status === 401 || shape.code === \"invalid_api_key\") {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.code === \"insufficient_quota\") {\n return new QuotaExceededError(message, { cause: thrown, context });\n }\n\n if (shape.status === 429 || shape.code === \"rate_limit_exceeded\") {\n return new ProviderRateLimitError(message, {\n cause: thrown,\n context,\n retryAfter: parseRetryAfter(shape.headers),\n });\n }\n\n if (shape.code === \"context_length_exceeded\") {\n return new ContextLengthExceededError(message, { cause: thrown, context });\n }\n\n if (shape.code === \"content_filter\") {\n return new ContentFilterError(message, {\n cause: thrown,\n context,\n reason: message,\n });\n }\n\n if (typeof shape.status === \"number\" && shape.status >= 400 && shape.status < 500) {\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n return new ProviderError(message, { cause: thrown, context });\n}\n\n/**\n * Read the raw error shape without depending on `instanceof APIError`\n * — some consumers wrap the SDK, and proxies sometimes strip the\n * prototype chain. Duck-typing on the visible fields is resilient to\n * both.\n */\nfunction toShape(thrown: unknown): OpenAIErrorShape {\n if (thrown instanceof OpenAI.APIError) {\n return {\n status: thrown.status,\n code: thrown.code,\n message: thrown.message,\n type: thrown.type,\n param: thrown.param,\n headers: thrown.headers as Record<string, string> | undefined,\n name: thrown.name,\n };\n }\n\n if (typeof thrown === \"object\" && thrown !== null) {\n const raw = thrown as Record<string, unknown>;\n\n return {\n status: typeof raw.status === \"number\" ? raw.status : undefined,\n code: typeof raw.code === \"string\" ? raw.code : undefined,\n message: typeof raw.message === \"string\" ? raw.message : undefined,\n type: typeof raw.type === \"string\" ? raw.type : undefined,\n param: typeof raw.param === \"string\" ? raw.param : undefined,\n headers:\n typeof raw.headers === \"object\" && raw.headers !== null\n ? (raw.headers as Record<string, string>)\n : undefined,\n name: typeof raw.name === \"string\" ? raw.name : undefined,\n };\n }\n\n return {};\n}\n\n/**\n * Decide whether the thrown value represents a timeout. OpenAI's SDK\n * throws `APIConnectionTimeoutError` for transport-level timeouts, and\n * Node surfaces `ETIMEDOUT` / `ECONNABORTED` on the lower socket\n * layer. Either signal counts.\n */\nfunction isTimeout(thrown: unknown, shape: OpenAIErrorShape): boolean {\n if (thrown instanceof OpenAI.APIConnectionTimeoutError) {\n return true;\n }\n\n if (shape.name === \"APIConnectionTimeoutError\") {\n return true;\n }\n\n if (shape.code === \"ETIMEDOUT\" || shape.code === \"ECONNABORTED\") {\n return true;\n }\n\n return false;\n}\n\n/**\n * Attach the raw diagnostic fields to `error.context` so consumers\n * have everything the provider surfaced without each subclass having\n * to redeclare them. Never includes `cause` — that lives on\n * `error.cause`.\n */\nfunction buildContext(\n thrown: unknown,\n shape: OpenAIErrorShape,\n): Record<string, unknown> {\n const context: Record<string, unknown> = {};\n\n if (shape.status !== undefined) {\n context.status = shape.status;\n }\n\n if (shape.code) {\n context.code = shape.code;\n }\n\n if (shape.type) {\n context.type = shape.type;\n }\n\n if (shape.param) {\n context.param = shape.param;\n }\n\n const requestId = readRequestId(thrown);\n\n if (requestId) {\n context.requestId = requestId;\n }\n\n return context;\n}\n\n/**\n * The request id OpenAI returned. Current SDKs expose it as\n * `APIError.requestID`; older ones and hand-rolled errors use\n * `request_id` / `requestId`. Extract defensively.\n */\nfunction readRequestId(thrown: unknown): string | undefined {\n if (typeof thrown !== \"object\" || thrown === null) {\n return undefined;\n }\n\n const raw = thrown as Record<string, unknown>;\n\n if (typeof raw.request_id === \"string\") {\n return raw.request_id;\n }\n\n if (typeof raw.requestId === \"string\") {\n return raw.requestId;\n }\n\n if (typeof raw.requestID === \"string\") {\n return raw.requestID;\n }\n\n return undefined;\n}\n\n/**\n * Parse the `Retry-After` response header (seconds per HTTP spec)\n * into milliseconds so consumers can feed it straight to `setTimeout`.\n * Returns `undefined` when missing or unparseable.\n */\nfunction parseRetryAfter(headers: Record<string, string> | undefined): number | undefined {\n if (!headers) {\n return undefined;\n }\n\n const raw = headers[\"retry-after\"] ?? headers[\"Retry-After\"];\n\n if (!raw) {\n return undefined;\n }\n\n const seconds = Number(raw);\n\n if (!Number.isFinite(seconds) || seconds < 0) {\n return undefined;\n }\n\n return Math.round(seconds * 1000);\n}\n"],"mappings":"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"}
|
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, 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 — `@warlock.js/ai/generate-images/SKILL.md`; 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`.'
|
|
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 — `@warlock.js/ai/generate-images/SKILL.md`; 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`.'
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# `@warlock.js/ai-openai`
|
|
@@ -53,7 +53,7 @@ Returns a `ModelContract` you pass straight into `ai.agent({ model })`.
|
|
|
53
53
|
| `structuredOutput` | `true`, unless `responseFormat` is set to `"json_object"` or `"text"` (loose modes) — then `false`. |
|
|
54
54
|
| `vision` | Inferred from model name. `true` for `gpt-4o*`, `gpt-4-turbo*`, `gpt-4.1*`, `o1*`, `o3*`, `chatgpt-4o*`; `false` otherwise. |
|
|
55
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` |
|
|
56
|
+
| `promptCaching` | `true` for the direct OpenAI provider. It supports automatic cache hits plus explicit content-part write breakpoints; OpenAI-compatible wrapper providers leave the OpenAI-only fields off. |
|
|
57
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
58
|
| `audio` | `false` by default (opt-in `.model({ audio: true })`). Only the `gpt-4o-audio-preview` family accepts `input_audio`. |
|
|
59
59
|
|
|
@@ -157,7 +157,32 @@ await model.complete(messages, { tools, reasoning: { effort: "high" } }); // you
|
|
|
157
157
|
await model.complete(messages, { tools, reasoning: { effort: "none" } }); // same effect as the default, explicit
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
The gpt-6 models (`gpt-6-astra` / `gpt-6-sol` / `gpt-6-luna`) are hard-400 cases, verified live on `gpt-6-sol`:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
400 Function tools with reasoning_effort are not supported for gpt-6-sol in /v1/chat/completions. To use function tools, use /v1/responses or set reasoning_effort to 'none'.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Tools work with reasoning off (the adapter's default when tools are present and no reasoning is set). For reasoning **and** tools together, use the Responses API below.
|
|
167
|
+
|
|
168
|
+
Calling **without** `tools` is unaffected — `reasoning_effort` is still omitted by default in that case (provider default reasoning, nothing to unblock). Trade-off of the `"none"` default: you lose reasoning on that call. For tool-heavy agent work (function calls + good replies rather than deep analysis) this is the right call — it's the difference between empty replies and working ones. When you need reasoning **and** tools together, use the Responses API (next section).
|
|
169
|
+
|
|
170
|
+
### Reasoning + tools: the Responses API (`api: "responses"`)
|
|
171
|
+
|
|
172
|
+
Opt a direct-OpenAI model into `/v1/responses`, where reasoning models accept function tools with reasoning on:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
const model = openai.model({ name: "gpt-6-sol", api: "responses" });
|
|
176
|
+
await model.complete(messages, { tools, reasoning: { effort: "high" } }); // reasoning stays on, tools work
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- Chat Completions stays the default; nothing changes unless you pass `api: "responses"`.
|
|
180
|
+
- Only the direct `openai` provider accepts it. `@warlock.js/ai-deepseek`, `ai-groq`, `ai-xai` and `ai-mistral` (which reuse this adapter with their own `baseURL`) reject `api: "responses"` with `InvalidRequestError`.
|
|
181
|
+
- With tools attached, reasoning uses the provider default instead of being forced to `"none"`: expect more reasoning tokens and latency than the Chat path. Pass `reasoning: { effort }` to choose.
|
|
182
|
+
- The system prompt is sent as `instructions`; requests use `store: false` (no server-side conversation state).
|
|
183
|
+
- Reasoning is carried across a tool loop without server storage: each response asks for encrypted reasoning, which rides on the first tool call of the turn (`providerMetadata.openaiResponses`) and is sent back on the next request when the model is the same. It is persisted with your conversation history.
|
|
184
|
+
- `complete()` and `stream()` both work; streamed chunks follow the same order as the Chat path (deltas, then tool calls, then `done`). Usage reports `reasoningTokens` and `cachedTokens` the same way.
|
|
185
|
+
- `pdf` and `audio` inputs are not available on this path.
|
|
161
186
|
|
|
162
187
|
## Token usage — what's reported
|
|
163
188
|
|
|
@@ -171,7 +196,21 @@ Calling **without** `tools` is unaffected — `reasoning_effort` is still omitte
|
|
|
171
196
|
|
|
172
197
|
Both `cachedTokens` and `reasoningTokens` are omitted (not set to `0`) when the provider reports zero, so non-reasoning / uncached calls keep the lean `{ input, output, total }` shape. Reported identically on `complete()` and the streaming `done` event.
|
|
173
198
|
|
|
174
|
-
|
|
199
|
+
### Explicit prompt caching (direct OpenAI only)
|
|
200
|
+
|
|
201
|
+
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
|
+
|
|
203
|
+
```ts
|
|
204
|
+
const model = openai.model({
|
|
205
|
+
name: "gpt-5.6",
|
|
206
|
+
promptCacheKey: "tenant:acme",
|
|
207
|
+
promptCacheRetention: "24h", // or "in_memory"
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`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
|
+
|
|
213
|
+
These fields are sent only when the SDK provider label is exactly `"openai"`. `DeepSeekSDK`, `GroqSDK`, `XaiSDK`, and `MistralSDK` label their wrapped endpoint differently, so their requests omit every OpenAI-only cache field even though they reuse this transport. Read-side `usage.cachedTokens` accounting remains available whenever the upstream reports it.
|
|
175
214
|
|
|
176
215
|
## Token counting
|
|
177
216
|
|
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, 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 — `@warlock.js/ai/generate-images/SKILL.md`; 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`.
|
|
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 — `@warlock.js/ai/generate-images/SKILL.md`; 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`.
|
package/package.json
CHANGED
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"url": "https://github.com/warlockjs/ai-openai"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"openai": "^7.
|
|
17
|
-
"@warlock.js/logger": "5.
|
|
16
|
+
"openai": "^7.23.0",
|
|
17
|
+
"@warlock.js/logger": "5.27.0"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@warlock.js/ai": "5.
|
|
20
|
+
"@warlock.js/ai": "5.27.0"
|
|
21
21
|
},
|
|
22
|
-
"version": "5.
|
|
22
|
+
"version": "5.27.0",
|
|
23
23
|
"main": "./cjs/index.cjs",
|
|
24
24
|
"module": "./esm/index.mjs",
|
|
25
25
|
"types": "./esm/index.d.mts",
|
|
@@ -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, 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 — `@warlock.js/ai/generate-images/SKILL.md`; 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`.'
|
|
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 — `@warlock.js/ai/generate-images/SKILL.md`; 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`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `@warlock.js/ai-openai`
|
|
@@ -45,7 +45,7 @@ Returns a `ModelContract` you pass straight into `ai.agent({ model })`.
|
|
|
45
45
|
| `structuredOutput` | `true`, unless `responseFormat` is set to `"json_object"` or `"text"` (loose modes) — then `false`. |
|
|
46
46
|
| `vision` | Inferred from model name. `true` for `gpt-4o*`, `gpt-4-turbo*`, `gpt-4.1*`, `o1*`, `o3*`, `chatgpt-4o*`; `false` otherwise. |
|
|
47
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` |
|
|
48
|
+
| `promptCaching` | `true` for the direct OpenAI provider. It supports automatic cache hits plus explicit content-part write breakpoints; OpenAI-compatible wrapper providers leave the OpenAI-only fields off. |
|
|
49
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
50
|
| `audio` | `false` by default (opt-in `.model({ audio: true })`). Only the `gpt-4o-audio-preview` family accepts `input_audio`. |
|
|
51
51
|
|
|
@@ -149,7 +149,32 @@ await model.complete(messages, { tools, reasoning: { effort: "high" } }); // you
|
|
|
149
149
|
await model.complete(messages, { tools, reasoning: { effort: "none" } }); // same effect as the default, explicit
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
The gpt-6 models (`gpt-6-astra` / `gpt-6-sol` / `gpt-6-luna`) are hard-400 cases, verified live on `gpt-6-sol`:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
400 Function tools with reasoning_effort are not supported for gpt-6-sol in /v1/chat/completions. To use function tools, use /v1/responses or set reasoning_effort to 'none'.
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Tools work with reasoning off (the adapter's default when tools are present and no reasoning is set). For reasoning **and** tools together, use the Responses API below.
|
|
159
|
+
|
|
160
|
+
Calling **without** `tools` is unaffected — `reasoning_effort` is still omitted by default in that case (provider default reasoning, nothing to unblock). Trade-off of the `"none"` default: you lose reasoning on that call. For tool-heavy agent work (function calls + good replies rather than deep analysis) this is the right call — it's the difference between empty replies and working ones. When you need reasoning **and** tools together, use the Responses API (next section).
|
|
161
|
+
|
|
162
|
+
### Reasoning + tools: the Responses API (`api: "responses"`)
|
|
163
|
+
|
|
164
|
+
Opt a direct-OpenAI model into `/v1/responses`, where reasoning models accept function tools with reasoning on:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const model = openai.model({ name: "gpt-6-sol", api: "responses" });
|
|
168
|
+
await model.complete(messages, { tools, reasoning: { effort: "high" } }); // reasoning stays on, tools work
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- Chat Completions stays the default; nothing changes unless you pass `api: "responses"`.
|
|
172
|
+
- Only the direct `openai` provider accepts it. `@warlock.js/ai-deepseek`, `ai-groq`, `ai-xai` and `ai-mistral` (which reuse this adapter with their own `baseURL`) reject `api: "responses"` with `InvalidRequestError`.
|
|
173
|
+
- With tools attached, reasoning uses the provider default instead of being forced to `"none"`: expect more reasoning tokens and latency than the Chat path. Pass `reasoning: { effort }` to choose.
|
|
174
|
+
- The system prompt is sent as `instructions`; requests use `store: false` (no server-side conversation state).
|
|
175
|
+
- Reasoning is carried across a tool loop without server storage: each response asks for encrypted reasoning, which rides on the first tool call of the turn (`providerMetadata.openaiResponses`) and is sent back on the next request when the model is the same. It is persisted with your conversation history.
|
|
176
|
+
- `complete()` and `stream()` both work; streamed chunks follow the same order as the Chat path (deltas, then tool calls, then `done`). Usage reports `reasoningTokens` and `cachedTokens` the same way.
|
|
177
|
+
- `pdf` and `audio` inputs are not available on this path.
|
|
153
178
|
|
|
154
179
|
## Token usage — what's reported
|
|
155
180
|
|
|
@@ -163,7 +188,21 @@ Calling **without** `tools` is unaffected — `reasoning_effort` is still omitte
|
|
|
163
188
|
|
|
164
189
|
Both `cachedTokens` and `reasoningTokens` are omitted (not set to `0`) when the provider reports zero, so non-reasoning / uncached calls keep the lean `{ input, output, total }` shape. Reported identically on `complete()` and the streaming `done` event.
|
|
165
190
|
|
|
166
|
-
|
|
191
|
+
### Explicit prompt caching (direct OpenAI only)
|
|
192
|
+
|
|
193
|
+
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
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const model = openai.model({
|
|
197
|
+
name: "gpt-5.6",
|
|
198
|
+
promptCacheKey: "tenant:acme",
|
|
199
|
+
promptCacheRetention: "24h", // or "in_memory"
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`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
|
+
|
|
205
|
+
These fields are sent only when the SDK provider label is exactly `"openai"`. `DeepSeekSDK`, `GroqSDK`, `XaiSDK`, and `MistralSDK` label their wrapped endpoint differently, so their requests omit every OpenAI-only cache field even though they reuse this transport. Read-side `usage.cachedTokens` accounting remains available whenever the upstream reports it.
|
|
167
206
|
|
|
168
207
|
## Token counting
|
|
169
208
|
|