@warlock.js/ai-openai 5.24.0 → 5.26.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 +20 -0
- package/cjs/index.cjs +108 -15
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +14 -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 +54 -8
- package/esm/model.mjs.map +1 -1
- package/esm/utils/to-openai-messages.mjs +40 -3
- package/esm/utils/to-openai-messages.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 +24 -2
- package/package.json +4 -4
- package/skills/setup-openai/SKILL.md +24 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to `@warlock.js/ai-openai` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.26.0 - 2026-09-30
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `reasoning.effort` now accepts every OpenAI SDK 7.23.0 `reasoning_effort` level: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`.
|
|
12
|
+
- OpenAI-labelled Chat Completions models now support explicit prompt caching: `promptCacheKey`, `promptCacheRetention`, and `cacheControl.breakpoints` map to the OpenAI SDK's request fields. These fields are deliberately omitted for OpenAI-compatible wrapper providers.
|
|
13
|
+
- Capability inference now recognizes the GPT-6 family as reasoning- and vision-capable, plus GPT-5 and o4 as vision-capable.
|
|
14
|
+
- **Behaviour change:** requests send `max_completion_tokens` and never the legacy `max_tokens`, which current OpenAI models (the gpt-5 family and later) reject with `400 unsupported_parameter`. `maxTokens` in config and call options is unchanged.
|
|
15
|
+
- Reasoning-capable models (o-series, gpt-5 family) no longer receive `temperature`; they accept only the default value and reject any other.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- A provider error now keeps the parameter OpenAI rejected (`context.param`) and the request id (`context.requestId`, read from the SDK's `requestID`), next to the status and code.
|
|
20
|
+
|
|
21
|
+
## 5.25.0 - 2026-09-28
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Lockstep release; package APIs are unchanged.
|
|
26
|
+
|
|
7
27
|
## 5.24.0 - 2026-09-27
|
|
8
28
|
|
|
9
29
|
### Changed
|
package/cjs/index.cjs
CHANGED
|
@@ -76,8 +76,8 @@ function mapFinishReason(raw) {
|
|
|
76
76
|
* ]},
|
|
77
77
|
* ]);
|
|
78
78
|
*/
|
|
79
|
-
function toOpenAIMessages(messages) {
|
|
80
|
-
return messages.map((m) => {
|
|
79
|
+
function toOpenAIMessages(messages, cacheBreakpoints = 0) {
|
|
80
|
+
return addPromptCacheBreakpoints(messages.map((m) => {
|
|
81
81
|
if (m.role === "tool") return {
|
|
82
82
|
role: "tool",
|
|
83
83
|
content: stringifyContent(m.content),
|
|
@@ -103,7 +103,44 @@ function toOpenAIMessages(messages) {
|
|
|
103
103
|
role: m.role,
|
|
104
104
|
content: stringifyContent(m.content)
|
|
105
105
|
};
|
|
106
|
-
});
|
|
106
|
+
}), cacheBreakpoints);
|
|
107
|
+
}
|
|
108
|
+
/** Place explicit markers on the last requested user-content parts (maximum four). */
|
|
109
|
+
function addPromptCacheBreakpoints(messages, requested) {
|
|
110
|
+
let remaining = Math.min(4, Math.max(0, Math.floor(requested)));
|
|
111
|
+
if (remaining === 0) return messages;
|
|
112
|
+
const marked = [...messages];
|
|
113
|
+
for (let index = marked.length - 1; index >= 0 && remaining > 0; index--) {
|
|
114
|
+
const message = marked[index];
|
|
115
|
+
if (message === void 0 || message.role !== "user") continue;
|
|
116
|
+
if (typeof message.content === "string") {
|
|
117
|
+
marked[index] = {
|
|
118
|
+
...message,
|
|
119
|
+
content: [{
|
|
120
|
+
type: "text",
|
|
121
|
+
text: message.content,
|
|
122
|
+
prompt_cache_breakpoint: { mode: "explicit" }
|
|
123
|
+
}]
|
|
124
|
+
};
|
|
125
|
+
remaining--;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const content = [...message.content];
|
|
129
|
+
for (let partIndex = content.length - 1; partIndex >= 0 && remaining > 0; partIndex--) {
|
|
130
|
+
const part = content[partIndex];
|
|
131
|
+
if (part === void 0) continue;
|
|
132
|
+
content[partIndex] = {
|
|
133
|
+
...part,
|
|
134
|
+
prompt_cache_breakpoint: { mode: "explicit" }
|
|
135
|
+
};
|
|
136
|
+
remaining--;
|
|
137
|
+
}
|
|
138
|
+
marked[index] = {
|
|
139
|
+
...message,
|
|
140
|
+
content
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return marked;
|
|
107
144
|
}
|
|
108
145
|
/**
|
|
109
146
|
* Multipart content is only meaningful on user messages — for any other
|
|
@@ -285,6 +322,7 @@ function toShape(thrown) {
|
|
|
285
322
|
code: thrown.code,
|
|
286
323
|
message: thrown.message,
|
|
287
324
|
type: thrown.type,
|
|
325
|
+
param: thrown.param,
|
|
288
326
|
headers: thrown.headers,
|
|
289
327
|
name: thrown.name
|
|
290
328
|
};
|
|
@@ -295,6 +333,7 @@ function toShape(thrown) {
|
|
|
295
333
|
code: typeof raw.code === "string" ? raw.code : void 0,
|
|
296
334
|
message: typeof raw.message === "string" ? raw.message : void 0,
|
|
297
335
|
type: typeof raw.type === "string" ? raw.type : void 0,
|
|
336
|
+
param: typeof raw.param === "string" ? raw.param : void 0,
|
|
298
337
|
headers: typeof raw.headers === "object" && raw.headers !== null ? raw.headers : void 0,
|
|
299
338
|
name: typeof raw.name === "string" ? raw.name : void 0
|
|
300
339
|
};
|
|
@@ -324,19 +363,22 @@ function buildContext(thrown, shape) {
|
|
|
324
363
|
if (shape.status !== void 0) context.status = shape.status;
|
|
325
364
|
if (shape.code) context.code = shape.code;
|
|
326
365
|
if (shape.type) context.type = shape.type;
|
|
366
|
+
if (shape.param) context.param = shape.param;
|
|
327
367
|
const requestId = readRequestId(thrown);
|
|
328
368
|
if (requestId) context.requestId = requestId;
|
|
329
369
|
return context;
|
|
330
370
|
}
|
|
331
371
|
/**
|
|
332
|
-
*
|
|
333
|
-
*
|
|
372
|
+
* The request id OpenAI returned. Current SDKs expose it as
|
|
373
|
+
* `APIError.requestID`; older ones and hand-rolled errors use
|
|
374
|
+
* `request_id` / `requestId`. Extract defensively.
|
|
334
375
|
*/
|
|
335
376
|
function readRequestId(thrown) {
|
|
336
377
|
if (typeof thrown !== "object" || thrown === null) return;
|
|
337
378
|
const raw = thrown;
|
|
338
379
|
if (typeof raw.request_id === "string") return raw.request_id;
|
|
339
380
|
if (typeof raw.requestId === "string") return raw.requestId;
|
|
381
|
+
if (typeof raw.requestID === "string") return raw.requestID;
|
|
340
382
|
}
|
|
341
383
|
/**
|
|
342
384
|
* Parse the `Retry-After` response header (seconds per HTTP spec)
|
|
@@ -599,11 +641,12 @@ const REASONING_CAPABLE_PREFIXES = [
|
|
|
599
641
|
"o1",
|
|
600
642
|
"o3",
|
|
601
643
|
"o4",
|
|
602
|
-
"gpt-5"
|
|
644
|
+
"gpt-5",
|
|
645
|
+
"gpt-6"
|
|
603
646
|
];
|
|
604
647
|
/**
|
|
605
648
|
* Infer whether a given OpenAI model name is a reasoning model (o-series
|
|
606
|
-
* and the gpt-5
|
|
649
|
+
* and the gpt-5 / gpt-6 families) based on the known-prefix list. Unknown models
|
|
607
650
|
* default to `false` so the adapter never forwards an unsupported
|
|
608
651
|
* `reasoning_effort` param to a non-reasoning model (which would 400).
|
|
609
652
|
*
|
|
@@ -611,6 +654,7 @@ const REASONING_CAPABLE_PREFIXES = [
|
|
|
611
654
|
* inferReasoningCapability("o3-mini"); // → true
|
|
612
655
|
* inferReasoningCapability("o4-mini"); // → true
|
|
613
656
|
* inferReasoningCapability("gpt-5-pro"); // → true
|
|
657
|
+
* inferReasoningCapability("gpt-6-astra"); // → true
|
|
614
658
|
* inferReasoningCapability("gpt-4o"); // → false
|
|
615
659
|
* inferReasoningCapability("custom-llm"); // → false
|
|
616
660
|
*/
|
|
@@ -638,8 +682,11 @@ const VISION_CAPABLE_PREFIXES = [
|
|
|
638
682
|
"gpt-4o",
|
|
639
683
|
"gpt-4-turbo",
|
|
640
684
|
"gpt-4.1",
|
|
685
|
+
"gpt-5",
|
|
686
|
+
"gpt-6",
|
|
641
687
|
"o1",
|
|
642
688
|
"o3",
|
|
689
|
+
"o4",
|
|
643
690
|
"chatgpt-4o"
|
|
644
691
|
];
|
|
645
692
|
/**
|
|
@@ -663,6 +710,23 @@ function inferVisionCapability(modelName) {
|
|
|
663
710
|
//#region ../ai-openai/src/model.ts
|
|
664
711
|
const LOG_MODULE$2 = "ai.openai";
|
|
665
712
|
/**
|
|
713
|
+
* The installed OpenAI SDK 7.23.0 declares `reasoning_effort` as
|
|
714
|
+
* `none | minimal | low | medium | high | xhigh | max` in
|
|
715
|
+
* `openai/src/resources/shared.ts:367`. Its type notes that model support
|
|
716
|
+
* varies but does not provide a model-family-specific accepted set, so map by
|
|
717
|
+
* that union alone. This exhaustive record makes a future core level a
|
|
718
|
+
* typecheck failure until its OpenAI clamp is consciously selected.
|
|
719
|
+
*/
|
|
720
|
+
const EFFORT_TO_OPENAI_EFFORT = {
|
|
721
|
+
none: "none",
|
|
722
|
+
minimal: "minimal",
|
|
723
|
+
low: "low",
|
|
724
|
+
medium: "medium",
|
|
725
|
+
high: "high",
|
|
726
|
+
xhigh: "xhigh",
|
|
727
|
+
max: "max"
|
|
728
|
+
};
|
|
729
|
+
/**
|
|
666
730
|
* Map an explicit `responseFormat` override to the default
|
|
667
731
|
* `structuredOutput` capability. Loose wire modes (`"json_object"`,
|
|
668
732
|
* `"text"`) don't enforce shape, so the agent needs to see the soft
|
|
@@ -724,7 +788,7 @@ var OpenAIModel = class {
|
|
|
724
788
|
structuredOutput: config.structuredOutput ?? inferStructuredOutput(config.responseFormat),
|
|
725
789
|
vision: config.vision ?? inferVisionCapability(config.name),
|
|
726
790
|
reasoning: config.reasoning ?? inferReasoningCapability(config.name),
|
|
727
|
-
promptCaching:
|
|
791
|
+
promptCaching: this.provider === "openai",
|
|
728
792
|
pdf: config.pdf ?? false,
|
|
729
793
|
audio: config.audio ?? false
|
|
730
794
|
};
|
|
@@ -746,9 +810,9 @@ var OpenAIModel = class {
|
|
|
746
810
|
try {
|
|
747
811
|
response = await this.client.chat.completions.create({
|
|
748
812
|
model: this.name,
|
|
749
|
-
messages: toOpenAIMessages(messages),
|
|
750
|
-
|
|
751
|
-
|
|
813
|
+
messages: toOpenAIMessages(messages, this.promptCacheBreakpoints(options)),
|
|
814
|
+
...this.buildSamplingParams(options),
|
|
815
|
+
...this.buildPromptCacheParams(),
|
|
752
816
|
tools: toOpenAITools(options?.tools),
|
|
753
817
|
...this.buildResponseFormat(options?.responseSchema),
|
|
754
818
|
...this.buildReasoningParams(options?.reasoning, Boolean(options?.tools?.length))
|
|
@@ -797,9 +861,9 @@ var OpenAIModel = class {
|
|
|
797
861
|
try {
|
|
798
862
|
stream = await this.client.chat.completions.create({
|
|
799
863
|
model: this.name,
|
|
800
|
-
messages: toOpenAIMessages(messages),
|
|
801
|
-
|
|
802
|
-
|
|
864
|
+
messages: toOpenAIMessages(messages, this.promptCacheBreakpoints(options)),
|
|
865
|
+
...this.buildSamplingParams(options),
|
|
866
|
+
...this.buildPromptCacheParams(),
|
|
803
867
|
tools: toOpenAITools(options?.tools),
|
|
804
868
|
stream: true,
|
|
805
869
|
stream_options: { include_usage: true },
|
|
@@ -880,6 +944,18 @@ var OpenAIModel = class {
|
|
|
880
944
|
usage
|
|
881
945
|
};
|
|
882
946
|
}
|
|
947
|
+
/** OpenAI-only request fields; wrappers use the same transport but not this wire extension. */
|
|
948
|
+
buildPromptCacheParams() {
|
|
949
|
+
if (this.provider !== "openai") return {};
|
|
950
|
+
return {
|
|
951
|
+
...this.config.promptCacheKey !== void 0 ? { prompt_cache_key: this.config.promptCacheKey } : {},
|
|
952
|
+
...this.config.promptCacheRetention !== void 0 ? { prompt_cache_retention: this.config.promptCacheRetention } : {}
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
/** Map the neutral cache hint only for the direct OpenAI provider. */
|
|
956
|
+
promptCacheBreakpoints(options) {
|
|
957
|
+
return this.provider === "openai" ? options?.cacheControl?.breakpoints ?? 0 : 0;
|
|
958
|
+
}
|
|
883
959
|
/**
|
|
884
960
|
* Translate the neutral `responseSchema` option into OpenAI's
|
|
885
961
|
* `response_format` parameter.
|
|
@@ -1029,10 +1105,27 @@ var OpenAIModel = class {
|
|
|
1029
1105
|
* Returns an empty spread when nothing applies, so the caller can
|
|
1030
1106
|
* unconditionally `...buildReasoningParams(...)` into the request.
|
|
1031
1107
|
*/
|
|
1108
|
+
/**
|
|
1109
|
+
* Output-length and sampling params, in current Chat Completions shapes only.
|
|
1110
|
+
*
|
|
1111
|
+
* - `max_completion_tokens`, never the legacy `max_tokens`: current
|
|
1112
|
+
* models (the gpt-5 family and later) answer `400 unsupported_parameter`
|
|
1113
|
+
* to `max_tokens`, and `max_completion_tokens` is accepted everywhere.
|
|
1114
|
+
* - No `temperature` for a reasoning-capable model: those accept only the
|
|
1115
|
+
* default value and reject any other with a 400.
|
|
1116
|
+
*/
|
|
1117
|
+
buildSamplingParams(options) {
|
|
1118
|
+
const maxTokens = options?.maxTokens ?? this.config.maxTokens;
|
|
1119
|
+
const temperature = options?.temperature ?? this.config.temperature;
|
|
1120
|
+
return {
|
|
1121
|
+
max_completion_tokens: maxTokens,
|
|
1122
|
+
...!this.capabilities.reasoning && { temperature }
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1032
1125
|
buildReasoningParams(reasoning, hasTools) {
|
|
1033
1126
|
if (!this.capabilities.reasoning) return {};
|
|
1034
1127
|
if (!reasoning?.effort) return hasTools ? { reasoning_effort: "none" } : {};
|
|
1035
|
-
return { reasoning_effort: reasoning.effort };
|
|
1128
|
+
return { reasoning_effort: EFFORT_TO_OPENAI_EFFORT[reasoning.effort] };
|
|
1036
1129
|
}
|
|
1037
1130
|
/**
|
|
1038
1131
|
* Reshape OpenAI's `tool_calls` array into the neutral
|