@warlock.js/ai-openai 5.25.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 CHANGED
@@ -4,6 +4,20 @@ 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
+
7
21
  ## 5.25.0 - 2026-09-28
8
22
 
9
23
  ### 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
- * OpenAI puts the request id on `APIError.request_id`. Extract
333
- * defensively — both camel and snake keys exist across SDK versions.
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 family) based on the known-prefix list. Unknown models
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: true,
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
- temperature: options?.temperature ?? this.config.temperature,
751
- max_tokens: options?.maxTokens ?? this.config.maxTokens,
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
- temperature: options?.temperature ?? this.config.temperature,
802
- max_tokens: options?.maxTokens ?? this.config.maxTokens,
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