@warlock.js/ai-openai 5.29.0 → 5.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +3 -3
  3. package/cjs/index.cjs +437 -69
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/config.type.d.mts +6 -4
  6. package/esm/config.type.d.mts.map +1 -1
  7. package/esm/embedder.d.mts +1 -1
  8. package/esm/embedder.d.mts.map +1 -1
  9. package/esm/embedder.mjs +1 -1
  10. package/esm/embedder.mjs.map +1 -1
  11. package/esm/image.d.mts +1 -1
  12. package/esm/image.d.mts.map +1 -1
  13. package/esm/image.mjs +1 -1
  14. package/esm/image.mjs.map +1 -1
  15. package/esm/index.d.mts +3 -1
  16. package/esm/index.mjs +2 -1
  17. package/esm/known-pricing.d.mts +35 -0
  18. package/esm/known-pricing.d.mts.map +1 -0
  19. package/esm/known-pricing.mjs +117 -0
  20. package/esm/known-pricing.mjs.map +1 -0
  21. package/esm/known-reasoning-models.mjs +1 -1
  22. package/esm/known-reasoning-models.mjs.map +1 -1
  23. package/esm/known-vision-models.mjs +1 -1
  24. package/esm/known-vision-models.mjs.map +1 -1
  25. package/esm/model.mjs +76 -24
  26. package/esm/model.mjs.map +1 -1
  27. package/esm/provider-options.d.mts +8 -0
  28. package/esm/provider-options.d.mts.map +1 -0
  29. package/esm/provider-options.mjs +49 -0
  30. package/esm/provider-options.mjs.map +1 -0
  31. package/esm/responses-model.mjs +117 -18
  32. package/esm/responses-model.mjs.map +1 -1
  33. package/esm/sdk.d.mts +4 -3
  34. package/esm/sdk.d.mts.map +1 -1
  35. package/esm/sdk.mjs +6 -4
  36. package/esm/sdk.mjs.map +1 -1
  37. package/esm/speech.d.mts +1 -1
  38. package/esm/speech.d.mts.map +1 -1
  39. package/esm/speech.mjs +1 -1
  40. package/esm/speech.mjs.map +1 -1
  41. package/esm/transcription.d.mts +1 -1
  42. package/esm/transcription.d.mts.map +1 -1
  43. package/esm/transcription.mjs +1 -1
  44. package/esm/transcription.mjs.map +1 -1
  45. package/esm/utils/build-usage.mjs +15 -3
  46. package/esm/utils/build-usage.mjs.map +1 -1
  47. package/esm/utils/index.mjs +1 -1
  48. package/esm/utils/map-finish-reason.mjs +1 -1
  49. package/esm/utils/map-finish-reason.mjs.map +1 -1
  50. package/esm/utils/map-responses-status.mjs +1 -1
  51. package/esm/utils/map-responses-status.mjs.map +1 -1
  52. package/esm/utils/prompt-cache-params.mjs +26 -7
  53. package/esm/utils/prompt-cache-params.mjs.map +1 -1
  54. package/esm/utils/reasoning-effort.mjs +1 -1
  55. package/esm/utils/reasoning-effort.mjs.map +1 -1
  56. package/esm/utils/reasoning-replay.mjs +24 -2
  57. package/esm/utils/reasoning-replay.mjs.map +1 -1
  58. package/esm/utils/structured-output.mjs +1 -1
  59. package/esm/utils/structured-output.mjs.map +1 -1
  60. package/esm/utils/to-openai-messages.mjs +1 -1
  61. package/esm/utils/to-openai-messages.mjs.map +1 -1
  62. package/esm/utils/to-openai-tools.mjs +1 -1
  63. package/esm/utils/to-openai-tools.mjs.map +1 -1
  64. package/esm/utils/to-responses-input.mjs +1 -1
  65. package/esm/utils/to-responses-input.mjs.map +1 -1
  66. package/esm/utils/to-responses-tools.mjs +1 -1
  67. package/esm/utils/to-responses-tools.mjs.map +1 -1
  68. package/esm/utils/wrap-openai-error.mjs +7 -2
  69. package/esm/utils/wrap-openai-error.mjs.map +1 -1
  70. package/llms-full.txt +128 -37
  71. package/llms.txt +1 -1
  72. package/package.json +5 -4
  73. package/skills/setup-openai/SKILL.md +128 -37
package/cjs/index.cjs CHANGED
@@ -31,28 +31,153 @@ openai = __toESM(openai, 1);
31
31
  let _warlock_js_ai = require("@warlock.js/ai");
32
32
  let _warlock_js_logger = require("@warlock.js/logger");
33
33
 
34
- //#region ../ai-openai/src/utils/build-usage.ts
34
+ //#region ../../packages/ai-openai/src/known-pricing.ts
35
+ /**
36
+ * Built-in OpenAI API list prices, USD per 1,000,000 tokens, keyed by
37
+ * exact model id. `cachedInput` is the cached-input rate. None of these
38
+ * models charges for cache writes.
39
+ *
40
+ * Source: https://developers.openai.com/api/docs/pricing (the target of
41
+ * platform.openai.com/docs/pricing), Standard tier, fetched 2026-10-17.
42
+ *
43
+ * Applied only when the SDK's provider label is `"openai"`, and only on
44
+ * an exact id match — dated snapshots can carry other prices. Left out on
45
+ * purpose, so pass `pricing` yourself for them:
46
+ *
47
+ * - Models priced by prompt length (a higher rate above 272K input
48
+ * tokens): the gpt-6 / gpt-5.6 / gpt-5.5 / gpt-5.4 families and the
49
+ * `-pro` models. A flat `ModelPricing` cannot express the tiers.
50
+ * - Promotional, Batch, Flex and Priority pricing, and the regional
51
+ * (data-residency) uplift.
52
+ */
53
+ const OPENAI_MODEL_PRICING = {
54
+ "gpt-5.4-mini": {
55
+ input: .75,
56
+ output: 4.5,
57
+ cachedInput: .075
58
+ },
59
+ "gpt-5.4-nano": {
60
+ input: .2,
61
+ output: 1.25,
62
+ cachedInput: .02
63
+ },
64
+ "gpt-5.2": {
65
+ input: 1.75,
66
+ output: 14,
67
+ cachedInput: .175
68
+ },
69
+ "gpt-5.1": {
70
+ input: 1.25,
71
+ output: 10,
72
+ cachedInput: .125
73
+ },
74
+ "gpt-5": {
75
+ input: 1.25,
76
+ output: 10,
77
+ cachedInput: .125
78
+ },
79
+ "gpt-5-mini": {
80
+ input: .25,
81
+ output: 2,
82
+ cachedInput: .025
83
+ },
84
+ "gpt-5-nano": {
85
+ input: .05,
86
+ output: .4,
87
+ cachedInput: .005
88
+ },
89
+ "gpt-4.1": {
90
+ input: 2,
91
+ output: 8,
92
+ cachedInput: .5
93
+ },
94
+ "gpt-4.1-mini": {
95
+ input: .4,
96
+ output: 1.6,
97
+ cachedInput: .1
98
+ },
99
+ "gpt-4.1-nano": {
100
+ input: .1,
101
+ output: .4,
102
+ cachedInput: .025
103
+ },
104
+ "gpt-4o": {
105
+ input: 2.5,
106
+ output: 10,
107
+ cachedInput: 1.25
108
+ },
109
+ "gpt-4o-mini": {
110
+ input: .15,
111
+ output: .6,
112
+ cachedInput: .075
113
+ },
114
+ o3: {
115
+ input: 2,
116
+ output: 8,
117
+ cachedInput: .5
118
+ },
119
+ "o4-mini": {
120
+ input: 1.1,
121
+ output: 4.4,
122
+ cachedInput: .275
123
+ },
124
+ "o3-mini": {
125
+ input: 1.1,
126
+ output: 4.4,
127
+ cachedInput: .55
128
+ },
129
+ o1: {
130
+ input: 15,
131
+ output: 60,
132
+ cachedInput: 7.5
133
+ }
134
+ };
135
+ /**
136
+ * Look up the built-in list price for an OpenAI model id (exact match).
137
+ * Returns `undefined` for unknown ids, so cost stays unknown rather than
138
+ * wrong.
139
+ *
140
+ * @example
141
+ * openAIPricingFor("gpt-4o-mini"); // { input: 0.15, output: 0.6, cachedInput: 0.075 }
142
+ * openAIPricingFor("gpt-5.5"); // undefined — priced by prompt length
143
+ */
144
+ function openAIPricingFor(modelName) {
145
+ return OPENAI_MODEL_PRICING[modelName];
146
+ }
147
+
148
+ //#endregion
149
+ //#region ../../packages/ai-openai/src/utils/build-usage.ts
35
150
  /**
36
151
  * Build the neutral `Usage` from extracted counts. The optional detail
37
152
  * counters are emitted only when the provider reports a positive value, so
38
153
  * non-reasoning / uncached calls keep the lean `{ input, output, total }`
39
154
  * shape. Shared by the Chat Completions and Responses adapters so both
40
155
  * apply the same rule.
156
+ *
157
+ * OpenAI-style usage counts cache reads and writes inside the prompt
158
+ * total (`input_tokens` includes `cached_tokens` and `cache_write_tokens`,
159
+ * and the compatible vendors — DeepSeek, xAI, Groq, Mistral — document
160
+ * the same), so the result is marked `inputIncludesCache: true`. When the
161
+ * model is priced, `cost` is attached.
41
162
  */
42
- function buildUsage(counts) {
163
+ function buildUsage(counts, pricing) {
43
164
  const { input, output, total, cachedTokens, reasoningTokens, cacheWriteTokens } = counts;
44
- return {
165
+ const usage = {
45
166
  input,
46
167
  output,
47
168
  total,
169
+ inputIncludesCache: true,
48
170
  ...cachedTokens !== void 0 && cachedTokens > 0 ? { cachedTokens } : {},
49
171
  ...reasoningTokens !== void 0 && reasoningTokens > 0 ? { reasoningTokens } : {},
50
172
  ...cacheWriteTokens !== void 0 && cacheWriteTokens > 0 ? { cacheWriteTokens } : {}
51
173
  };
174
+ const cost = (0, _warlock_js_ai.computeCost)(usage, pricing);
175
+ if (cost) usage.cost = cost;
176
+ return usage;
52
177
  }
53
178
 
54
179
  //#endregion
55
- //#region ../ai-openai/src/utils/wrap-openai-error.ts
180
+ //#region ../../packages/ai-openai/src/utils/wrap-openai-error.ts
56
181
  /**
57
182
  * Wrap any thrown value caught inside the OpenAI adapter into the
58
183
  * appropriate `@warlock.js/ai` `AIError` subclass.
@@ -109,6 +234,11 @@ function wrapOpenAIError(thrown) {
109
234
  cause: thrown,
110
235
  context
111
236
  });
237
+ if (shape.code === "server_is_overloaded" || shape.status === 503) return new _warlock_js_ai.ProviderOverloadedError(message, {
238
+ cause: thrown,
239
+ context,
240
+ retryAfter: parseRetryAfter(shape.headers)
241
+ });
112
242
  return new _warlock_js_ai.ProviderError(message, {
113
243
  cause: thrown,
114
244
  context
@@ -199,7 +329,7 @@ function parseRetryAfter(headers) {
199
329
  }
200
330
 
201
331
  //#endregion
202
- //#region ../ai-openai/src/utils/map-responses-status.ts
332
+ //#region ../../packages/ai-openai/src/utils/map-responses-status.ts
203
333
  /**
204
334
  * Derive our `FinishReason` from a Responses terminal state. Responses has
205
335
  * no `finish_reason`, so it comes from `status`, `incomplete_details` and the
@@ -249,7 +379,7 @@ function toResponseFailure(response, model) {
249
379
  }
250
380
 
251
381
  //#endregion
252
- //#region ../ai-openai/src/utils/map-finish-reason.ts
382
+ //#region ../../packages/ai-openai/src/utils/map-finish-reason.ts
253
383
  const finishReasonMap = {
254
384
  stop: "stop",
255
385
  tool_calls: "tool_calls",
@@ -269,22 +399,41 @@ function mapFinishReason(raw) {
269
399
  }
270
400
 
271
401
  //#endregion
272
- //#region ../ai-openai/src/utils/prompt-cache-params.ts
402
+ //#region ../../packages/ai-openai/src/utils/prompt-cache-params.ts
403
+ /**
404
+ * Provider labels whose OpenAI-compatible endpoint documents a
405
+ * `prompt_cache_key` request field:
406
+ *
407
+ * - `openai` — https://developers.openai.com/api/docs/guides/prompt-caching
408
+ * - `xai` — Chat Completions accepts `prompt_cache_key` ("plumbed to
409
+ * x-grok-conv-id"), https://docs.x.ai/openapi.json and
410
+ * https://docs.x.ai/developers/advanced-api-usage/prompt-caching/maximizing-cache-hits
411
+ * - `mistral` — https://docs.mistral.ai/studio/conversations/advanced/prompt-caching
412
+ *
413
+ * Groq and DeepSeek cache automatically and document no request hint, so
414
+ * they receive nothing (checked 2026-10-17).
415
+ */
416
+ const PROMPT_CACHE_KEY_PROVIDERS = new Set([
417
+ "openai",
418
+ "xai",
419
+ "mistral"
420
+ ]);
273
421
  /**
274
- * Build the OpenAI-only prompt-cache request fields from the model config.
275
- * Returns nothing for any provider label other than `"openai"`: wrapper
276
- * endpoints use the same transport but may reject these OpenAI-only fields.
422
+ * Build the prompt-cache request fields from the model config.
423
+ * `prompt_cache_key` is sent for the providers that document it
424
+ * ({@link PROMPT_CACHE_KEY_PROVIDERS}); `prompt_cache_retention` is
425
+ * OpenAI-only. Any other provider label — including a custom label on a
426
+ * wrapper SDK — gets neither, since an unknown endpoint may reject them.
277
427
  */
278
428
  function buildPromptCacheParams(provider, config) {
279
- if (provider !== "openai") return {};
280
429
  return {
281
- ...config.promptCacheKey !== void 0 ? { prompt_cache_key: config.promptCacheKey } : {},
282
- ...config.promptCacheRetention !== void 0 ? { prompt_cache_retention: config.promptCacheRetention } : {}
430
+ ...PROMPT_CACHE_KEY_PROVIDERS.has(provider) && config.promptCacheKey !== void 0 ? { prompt_cache_key: config.promptCacheKey } : {},
431
+ ...provider === "openai" && config.promptCacheRetention !== void 0 ? { prompt_cache_retention: config.promptCacheRetention } : {}
283
432
  };
284
433
  }
285
434
 
286
435
  //#endregion
287
- //#region ../ai-openai/src/utils/reasoning-effort.ts
436
+ //#region ../../packages/ai-openai/src/utils/reasoning-effort.ts
288
437
  /**
289
438
  * The installed OpenAI SDK 7.23.0 declares `reasoning_effort` as
290
439
  * `none | minimal | low | medium | high | xhigh | max` in
@@ -317,7 +466,7 @@ function toOpenAIReasoningEffort(effort) {
317
466
  }
318
467
 
319
468
  //#endregion
320
- //#region ../ai-openai/src/utils/structured-output.ts
469
+ //#region ../../packages/ai-openai/src/utils/structured-output.ts
321
470
  /**
322
471
  * Map an explicit `responseFormat` override to the default
323
472
  * `structuredOutput` capability. Loose wire modes (`"json_object"`,
@@ -399,7 +548,7 @@ function isStrictSafeNode(node) {
399
548
  }
400
549
 
401
550
  //#endregion
402
- //#region ../ai-openai/src/utils/reasoning-replay.ts
551
+ //#region ../../packages/ai-openai/src/utils/reasoning-replay.ts
403
552
  /**
404
553
  * Key under `ModelToolCallRequest.providerMetadata` owned by the Responses
405
554
  * adapter. This name and the shape below are PERSISTED in users' conversation
@@ -415,6 +564,28 @@ const RESPONSES_METADATA_KEY = "openaiResponses";
415
564
  function pickReplayableReasoning(items) {
416
565
  return items.filter((item) => typeof item.encrypted_content === "string" && item.encrypted_content !== "");
417
566
  }
567
+ /** Output item types that may follow a replayed reasoning item. */
568
+ const REPLAY_SAFE_FOLLOWERS = new Set(["function_call", "message"]);
569
+ /**
570
+ * The reasoning items of a turn that are safe to replay, in order.
571
+ *
572
+ * The API pairs a reasoning item with the output item that followed it and
573
+ * rejects a replayed reasoning item whose follower is missing. Only
574
+ * `function_call` items are replayed, so a reasoning item that led into a
575
+ * hosted (server-run) tool call — `web_search_call`, `file_search_call`,
576
+ * `code_interpreter_call`, … from a provider-defined tool — is dropped
577
+ * rather than sent without its follower. Encrypted reasoning only improves
578
+ * continuity; dropping it never breaks the request.
579
+ */
580
+ function pickTurnReasoning(output) {
581
+ const picked = [];
582
+ output.forEach((item, index) => {
583
+ if (item.type !== "reasoning") return;
584
+ const follower = output.slice(index + 1).find((next) => next.type !== "reasoning");
585
+ if (follower === void 0 || REPLAY_SAFE_FOLLOWERS.has(follower.type)) picked.push(item);
586
+ });
587
+ return picked;
588
+ }
418
589
  /**
419
590
  * Attach the turn's reasoning items to the FIRST tool call only (parallel
420
591
  * calls do not each repeat the blobs). Returns the calls unchanged when
@@ -482,7 +653,7 @@ function isReplayMetadata(value) {
482
653
  }
483
654
 
484
655
  //#endregion
485
- //#region ../ai-openai/src/utils/to-openai-messages.ts
656
+ //#region ../../packages/ai-openai/src/utils/to-openai-messages.ts
486
657
  /**
487
658
  * Convert vendor-neutral Message[] to OpenAI's chat message shape.
488
659
  * Handles the `tool` role (requires `tool_call_id`) and assistant messages
@@ -649,7 +820,7 @@ function toOpenAIAudioFormat(mediaType) {
649
820
  }
650
821
 
651
822
  //#endregion
652
- //#region ../ai-openai/src/utils/to-openai-tools.ts
823
+ //#region ../../packages/ai-openai/src/utils/to-openai-tools.ts
653
824
  /**
654
825
  * Convert vendor-neutral ToolConfig[] to OpenAI's tools array.
655
826
  * Uses the shared `extractJsonSchema` helper; falls back to an empty-object
@@ -686,7 +857,7 @@ function toToolParameters(input) {
686
857
  }
687
858
 
688
859
  //#endregion
689
- //#region ../ai-openai/src/utils/to-responses-input.ts
860
+ //#region ../../packages/ai-openai/src/utils/to-responses-input.ts
690
861
  /**
691
862
  * Convert vendor-neutral `Message[]` into the Responses request pieces.
692
863
  *
@@ -792,7 +963,7 @@ function toResponsesContentPart(part) {
792
963
  }
793
964
 
794
965
  //#endregion
795
- //#region ../ai-openai/src/utils/to-responses-tools.ts
966
+ //#region ../../packages/ai-openai/src/utils/to-responses-tools.ts
796
967
  /**
797
968
  * Convert vendor-neutral `ToolConfig[]` to Responses function tools.
798
969
  *
@@ -839,7 +1010,7 @@ function toResponsesToolChoice(toolChoice) {
839
1010
  }
840
1011
 
841
1012
  //#endregion
842
- //#region ../ai-openai/src/embedder.ts
1013
+ //#region ../../packages/ai-openai/src/embedder.ts
843
1014
  const LOG_MODULE$5 = "ai.openai";
844
1015
  /**
845
1016
  * OpenAI-backed implementation of `EmbedderContract`.
@@ -948,7 +1119,7 @@ var OpenAIEmbedder = class {
948
1119
  };
949
1120
 
950
1121
  //#endregion
951
- //#region ../ai-openai/src/image.ts
1122
+ //#region ../../packages/ai-openai/src/image.ts
952
1123
  const LOG_MODULE$4 = "ai.openai";
953
1124
  /** Map a neutral output container to its IANA media type. */
954
1125
  function mediaTypeFor(format) {
@@ -1066,7 +1237,52 @@ var OpenAIImageModel = class {
1066
1237
  };
1067
1238
 
1068
1239
  //#endregion
1069
- //#region ../ai-openai/src/known-reasoning-models.ts
1240
+ //#region ../../packages/ai-openai/src/provider-options.ts
1241
+ const RESERVED_FIELDS = new Set([
1242
+ "model",
1243
+ "messages",
1244
+ "input",
1245
+ "instructions",
1246
+ "tools",
1247
+ "stream",
1248
+ "stream_options",
1249
+ "previous_response_id",
1250
+ "conversation",
1251
+ "store",
1252
+ "include",
1253
+ "functions",
1254
+ "function_call",
1255
+ "prompt",
1256
+ "background"
1257
+ ]);
1258
+ function mergeProviderBody(built, options) {
1259
+ const body = (options?.providerOptions?.openai)?.body ?? {};
1260
+ const reserved = Object.keys(body).filter((key) => RESERVED_FIELDS.has(key));
1261
+ if (reserved.length > 0) throw new _warlock_js_ai.InvalidRequestError(`providerOptions.openai.body cannot set ${reserved.join(", ")}: use the call's messages, tools and model options.`, { context: { fields: reserved } });
1262
+ const defaults = built;
1263
+ const overrides = Object.fromEntries(Object.entries(body).map(([key, value]) => {
1264
+ const previous = defaults[key];
1265
+ if (!isPlainObject(previous) || !isPlainObject(value)) return [key, value];
1266
+ if (typeof previous.type === "string" && typeof value.type === "string" && previous.type !== value.type) return [key, value];
1267
+ return [key, {
1268
+ ...previous,
1269
+ ...value
1270
+ }];
1271
+ }));
1272
+ return {
1273
+ ...built,
1274
+ ...overrides
1275
+ };
1276
+ }
1277
+ /** Merge only plain objects; arrays, null and provider scalar values replace the field. */
1278
+ function isPlainObject(value) {
1279
+ if (value === null || typeof value !== "object") return false;
1280
+ const prototype = Object.getPrototypeOf(value);
1281
+ return prototype === Object.prototype || prototype === null;
1282
+ }
1283
+
1284
+ //#endregion
1285
+ //#region ../../packages/ai-openai/src/known-reasoning-models.ts
1070
1286
  /**
1071
1287
  * Model-name prefixes for OpenAI families that expose internal
1072
1288
  * reasoning / thinking tokens and accept the `reasoning_effort`
@@ -1108,7 +1324,7 @@ function inferReasoningCapability(modelName) {
1108
1324
  }
1109
1325
 
1110
1326
  //#endregion
1111
- //#region ../ai-openai/src/known-vision-models.ts
1327
+ //#region ../../packages/ai-openai/src/known-vision-models.ts
1112
1328
  /**
1113
1329
  * Model-name prefixes for OpenAI families that support vision input
1114
1330
  * (image attachments) on the Chat Completions API.
@@ -1151,7 +1367,7 @@ function inferVisionCapability(modelName) {
1151
1367
  }
1152
1368
 
1153
1369
  //#endregion
1154
- //#region ../ai-openai/src/model.ts
1370
+ //#region ../../packages/ai-openai/src/model.ts
1155
1371
  const LOG_MODULE$3 = "ai.openai";
1156
1372
  /**
1157
1373
  * OpenAI-backed implementation of `ModelContract`.
@@ -1227,15 +1443,15 @@ var OpenAIModel = class {
1227
1443
  });
1228
1444
  let response;
1229
1445
  try {
1230
- response = await this.client.chat.completions.create({
1446
+ response = await this.client.chat.completions.create(mergeProviderBody({
1231
1447
  model: this.name,
1232
1448
  messages: toOpenAIMessages(messages, this.promptCacheBreakpoints(options)),
1233
1449
  ...this.buildSamplingParams(options),
1234
1450
  ...buildPromptCacheParams(this.provider, this.config),
1235
- tools: toOpenAITools(options?.tools),
1451
+ ...this.buildTools(options),
1236
1452
  ...this.buildResponseFormat(options?.responseSchema),
1237
1453
  ...this.buildReasoningParams(options?.reasoning, Boolean(options?.tools?.length))
1238
- }, options?.signal ? { signal: options.signal } : void 0);
1454
+ }, options), options?.signal ? { signal: options.signal } : void 0);
1239
1455
  } catch (thrown) {
1240
1456
  const wrapped = wrapOpenAIError(thrown);
1241
1457
  this.logger.error(LOG_MODULE$3, "error", wrapped.message, {
@@ -1268,6 +1484,11 @@ var OpenAIModel = class {
1268
1484
  * `delta` for text tokens, `tool-call` when the model requests a tool,
1269
1485
  * and a terminal `done` carrying the final finish reason + usage totals.
1270
1486
  * Callers consume it with `for await`.
1487
+ *
1488
+ * With `options.streamChunks` it also yields `tool-args` for each
1489
+ * `tool_calls[].function.arguments` fragment and `reasoning` for a
1490
+ * vendor's `reasoning_content` / `reasoning` delta field. Usage arrives
1491
+ * only on the final chunk here, so no `usage` chunk is emitted.
1271
1492
  */
1272
1493
  async *stream(messages, options) {
1273
1494
  this.logger.debug(LOG_MODULE$3, "request", "Starting streaming call to chat.completions", {
@@ -1278,17 +1499,17 @@ var OpenAIModel = class {
1278
1499
  });
1279
1500
  let stream;
1280
1501
  try {
1281
- stream = await this.client.chat.completions.create({
1502
+ stream = await this.client.chat.completions.create(mergeProviderBody({
1282
1503
  model: this.name,
1283
1504
  messages: toOpenAIMessages(messages, this.promptCacheBreakpoints(options)),
1284
1505
  ...this.buildSamplingParams(options),
1285
1506
  ...buildPromptCacheParams(this.provider, this.config),
1286
- tools: toOpenAITools(options?.tools),
1507
+ ...this.buildTools(options),
1287
1508
  stream: true,
1288
1509
  stream_options: { include_usage: true },
1289
1510
  ...this.buildResponseFormat(options?.responseSchema),
1290
1511
  ...this.buildReasoningParams(options?.reasoning, Boolean(options?.tools?.length))
1291
- }, options?.signal ? { signal: options.signal } : void 0);
1512
+ }, options), options?.signal ? { signal: options.signal } : void 0);
1292
1513
  } catch (thrown) {
1293
1514
  const wrapped = wrapOpenAIError(thrown);
1294
1515
  this.logger.error(LOG_MODULE$3, "error", wrapped.message, {
@@ -1298,16 +1519,24 @@ var OpenAIModel = class {
1298
1519
  throw wrapped;
1299
1520
  }
1300
1521
  let rawFinishReason = "stop";
1301
- const usage = {
1522
+ let usage = {
1302
1523
  input: 0,
1303
1524
  output: 0,
1304
1525
  total: 0
1305
1526
  };
1527
+ const want = (0, _warlock_js_ai.resolveStreamChunks)(options);
1306
1528
  const toolCallAccum = /* @__PURE__ */ new Map();
1307
1529
  try {
1308
1530
  for await (const chunk of stream) {
1309
1531
  const delta = chunk.choices[0]?.delta;
1310
1532
  const finish = chunk.choices[0]?.finish_reason;
1533
+ if (want.reasoning && delta) {
1534
+ const reasoning = readReasoningDelta(delta);
1535
+ if (reasoning) yield {
1536
+ type: "reasoning",
1537
+ delta: reasoning
1538
+ };
1539
+ }
1311
1540
  if (delta?.content) yield {
1312
1541
  type: "delta",
1313
1542
  content: delta.content
@@ -1317,23 +1546,25 @@ var OpenAIModel = class {
1317
1546
  if (!toolCallAccum.has(idx)) toolCallAccum.set(idx, {
1318
1547
  id: "",
1319
1548
  name: "",
1320
- arguments: ""
1549
+ arguments: "",
1550
+ streamed: 0
1321
1551
  });
1322
1552
  const acc = toolCallAccum.get(idx);
1323
1553
  if (toolCall.id) acc.id = toolCall.id;
1324
1554
  if (toolCall.function?.name) acc.name = toolCall.function.name;
1325
1555
  if (toolCall.function?.arguments) acc.arguments += toolCall.function.arguments;
1556
+ if (want.toolArgs && acc.name && acc.arguments.length > acc.streamed) {
1557
+ yield {
1558
+ type: "tool-args",
1559
+ callId: acc.id,
1560
+ name: acc.name,
1561
+ delta: acc.arguments.slice(acc.streamed)
1562
+ };
1563
+ acc.streamed = acc.arguments.length;
1564
+ }
1326
1565
  }
1327
1566
  if (finish) rawFinishReason = finish;
1328
- if (chunk.usage) {
1329
- usage.input = chunk.usage.prompt_tokens ?? 0;
1330
- usage.output = chunk.usage.completion_tokens ?? 0;
1331
- usage.total = chunk.usage.total_tokens ?? 0;
1332
- const cached = chunk.usage.prompt_tokens_details?.cached_tokens;
1333
- if (cached !== void 0 && cached > 0) usage.cachedTokens = cached;
1334
- const reasoning = chunk.usage.completion_tokens_details?.reasoning_tokens;
1335
- if (reasoning !== void 0 && reasoning > 0) usage.reasoningTokens = reasoning;
1336
- }
1567
+ if (chunk.usage) usage = this.extractUsage(chunk.usage);
1337
1568
  }
1338
1569
  for (const acc of toolCallAccum.values()) {
1339
1570
  if (!acc.name) continue;
@@ -1363,6 +1594,25 @@ var OpenAIModel = class {
1363
1594
  usage
1364
1595
  };
1365
1596
  }
1597
+ /**
1598
+ * Spread-friendly `tools` fragment: the function tools, then the
1599
+ * provider-defined entries whose `provider` equals this model's
1600
+ * provider label (`"openai"`, or the vendor label an OpenAI-compatible
1601
+ * wrapper sets, e.g. `"groq"`), sent verbatim. Chat Completions only
1602
+ * accepts `function` and `custom` tool types from OpenAI itself; its
1603
+ * hosted tools (`web_search`, `file_search`, …) need `api: "responses"`
1604
+ * and OpenAI rejects them here with a 400.
1605
+ */
1606
+ buildTools(options) {
1607
+ const { tools, definitions } = (0, _warlock_js_ai.selectProviderTools)(options, {
1608
+ accepts: [this.provider],
1609
+ module: LOG_MODULE$3,
1610
+ model: this.name,
1611
+ logger: this.logger
1612
+ });
1613
+ const mapped = [...toOpenAITools(tools) ?? [], ...definitions];
1614
+ return mapped.length > 0 ? { tools: mapped } : {};
1615
+ }
1366
1616
  /** Map the neutral cache hint only for the direct OpenAI provider. */
1367
1617
  promptCacheBreakpoints(options) {
1368
1618
  return this.provider === "openai" ? options?.cacheControl?.breakpoints ?? 0 : 0;
@@ -1401,7 +1651,9 @@ var OpenAIModel = class {
1401
1651
  * (the hidden reasoning channel on o-series / gpt-5 models, already
1402
1652
  * counted within `output`). Both are emitted only when the provider
1403
1653
  * reports a positive value, so non-reasoning / uncached calls keep the
1404
- * lean `{ input, output, total }` shape.
1654
+ * lean `{ input, output, total }` shape. `cacheWriteTokens` mirrors
1655
+ * `prompt_tokens_details.cache_write_tokens` (billed writes on the
1656
+ * models that charge for them). `cost` is attached when priced.
1405
1657
  */
1406
1658
  extractUsage(raw) {
1407
1659
  if (!raw) return {
@@ -1410,12 +1662,13 @@ var OpenAIModel = class {
1410
1662
  total: 0
1411
1663
  };
1412
1664
  return buildUsage({
1413
- input: raw.prompt_tokens,
1414
- output: raw.completion_tokens,
1415
- total: raw.total_tokens,
1665
+ input: raw.prompt_tokens ?? 0,
1666
+ output: raw.completion_tokens ?? 0,
1667
+ total: raw.total_tokens ?? 0,
1416
1668
  cachedTokens: raw.prompt_tokens_details?.cached_tokens,
1669
+ cacheWriteTokens: raw.prompt_tokens_details?.cache_write_tokens,
1417
1670
  reasoningTokens: raw.completion_tokens_details?.reasoning_tokens
1418
- });
1671
+ }, this.pricing);
1419
1672
  }
1420
1673
  /**
1421
1674
  * Translate the neutral `ModelCallOptions.reasoning` hint into OpenAI's
@@ -1491,11 +1744,38 @@ var OpenAIModel = class {
1491
1744
  }));
1492
1745
  }
1493
1746
  };
1747
+ /**
1748
+ * Reasoning text an OpenAI-compatible vendor puts on a Chat Completions
1749
+ * stream delta. OpenAI itself streams none on this API. DeepSeek documents
1750
+ * `delta.reasoning_content` (API reference, chat completion chunk); Groq
1751
+ * documents the parsed reasoning under a `reasoning` field. Neither is in
1752
+ * the OpenAI SDK's delta type, so the delta is read as a plain record.
1753
+ */
1754
+ function readReasoningDelta(delta) {
1755
+ const record = delta;
1756
+ for (const key of ["reasoning_content", "reasoning"]) {
1757
+ const value = record[key];
1758
+ if (typeof value === "string" && value !== "") return value;
1759
+ }
1760
+ }
1494
1761
 
1495
1762
  //#endregion
1496
- //#region ../ai-openai/src/responses-model.ts
1763
+ //#region ../../packages/ai-openai/src/responses-model.ts
1497
1764
  const LOG_MODULE$2 = "ai.openai";
1498
1765
  /**
1766
+ * Responses output item types of built-in tools the CALLER must execute
1767
+ * (installed SDK `ResponseOutputItem` union). The adapter maps only
1768
+ * `function_call` to a neutral tool call, so these fail closed.
1769
+ */
1770
+ const CLIENT_RUN_ITEM_TYPES = new Set([
1771
+ "computer_call",
1772
+ "custom_tool_call",
1773
+ "local_shell_call",
1774
+ "shell_call",
1775
+ "apply_patch_call",
1776
+ "mcp_approval_request"
1777
+ ]);
1778
+ /**
1499
1779
  * OpenAI Responses API implementation of `ModelContract` (`api: "responses"`).
1500
1780
  *
1501
1781
  * **Role.** A sibling of `OpenAIModel` that speaks `POST /v1/responses`
@@ -1522,7 +1802,16 @@ const LOG_MODULE$2 = "ai.openai";
1522
1802
  *
1523
1803
  * **Streaming.** `stream()` uses `responses.create({ stream: true })` and yields
1524
1804
  * the same chunk sequence as the Chat path: text `delta`s, then the
1525
- * `tool-call`s, then one `done`.
1805
+ * `tool-call`s, then one `done`. With `streamChunks` it also yields
1806
+ * `reasoning` (reasoning summary / reasoning text deltas) and `tool-args`
1807
+ * (`response.function_call_arguments.delta`).
1808
+ *
1809
+ * **Provider-defined tools.** `ai.providerTool({ provider: "openai" })`
1810
+ * definitions are appended to `tools` verbatim. Hosted tools run inside the
1811
+ * response; their output items (`web_search_call`, …) are not replayed
1812
+ * (with `store: false` the next turn only needs the answer text), and a
1813
+ * reasoning item that led into one is not replayed either (see
1814
+ * `pickTurnReasoning`).
1526
1815
  */
1527
1816
  var OpenAIResponsesModel = class {
1528
1817
  constructor(client, config, provider = "openai") {
@@ -1585,6 +1874,26 @@ var OpenAIResponsesModel = class {
1585
1874
  ...toolCalls ? { toolCalls } : {}
1586
1875
  };
1587
1876
  }
1877
+ /** Count native Responses input tokens using the same converted input and tools. */
1878
+ async countTokens(messages, options) {
1879
+ const request = this.buildRequest(messages, options);
1880
+ const body = {
1881
+ model: request.model,
1882
+ input: request.input,
1883
+ instructions: request.instructions,
1884
+ tools: request.tools,
1885
+ tool_choice: request.tool_choice,
1886
+ reasoning: request.reasoning,
1887
+ text: request.text,
1888
+ parallel_tool_calls: request.parallel_tool_calls,
1889
+ truncation: request.truncation ?? void 0
1890
+ };
1891
+ try {
1892
+ return (await this.client.responses.inputTokens.count(body, options?.signal ? { signal: options.signal } : void 0)).input_tokens;
1893
+ } catch (thrown) {
1894
+ throw this.logAndWrap(thrown);
1895
+ }
1896
+ }
1588
1897
  /**
1589
1898
  * Incremental streaming completion via `responses.create({ stream: true })`
1590
1899
  * (the SDK's `Stream<ResponseStreamEvent>`, `responses.d.ts:55`). Yields the
@@ -1619,7 +1928,9 @@ var OpenAIResponsesModel = class {
1619
1928
  throw this.logAndWrap(thrown);
1620
1929
  }
1621
1930
  const toolCalls = [];
1622
- const reasoningItems = [];
1931
+ const outputItems = [];
1932
+ const want = (0, _warlock_js_ai.resolveStreamChunks)(options);
1933
+ const streamingCalls = /* @__PURE__ */ new Map();
1623
1934
  let terminal;
1624
1935
  try {
1625
1936
  for await (const event of events) {
@@ -1630,10 +1941,36 @@ var OpenAIResponsesModel = class {
1630
1941
  };
1631
1942
  continue;
1632
1943
  }
1944
+ if (event.type === "response.reasoning_summary_text.delta" || event.type === "response.reasoning_text.delta") {
1945
+ if (want.reasoning && event.delta) yield {
1946
+ type: "reasoning",
1947
+ delta: event.delta
1948
+ };
1949
+ continue;
1950
+ }
1951
+ if (event.type === "response.output_item.added") {
1952
+ const { item } = event;
1953
+ if (item.type === "function_call" && item.id) streamingCalls.set(item.id, {
1954
+ callId: item.call_id,
1955
+ name: item.name
1956
+ });
1957
+ continue;
1958
+ }
1959
+ if (event.type === "response.function_call_arguments.delta") {
1960
+ const call = streamingCalls.get(event.item_id);
1961
+ if (want.toolArgs && call && event.delta) yield {
1962
+ type: "tool-args",
1963
+ callId: call.callId,
1964
+ name: call.name,
1965
+ delta: event.delta
1966
+ };
1967
+ continue;
1968
+ }
1633
1969
  if (event.type === "response.output_item.done") {
1634
1970
  const { item } = event;
1635
- if (item.type === "reasoning") reasoningItems.push(item);
1636
- else if (item.type === "function_call" && item.status !== "incomplete") toolCalls.push(this.toToolCall(item));
1971
+ this.assertServerRunItem(item);
1972
+ outputItems.push(item);
1973
+ if (item.type === "function_call" && item.status !== "incomplete") toolCalls.push(this.toToolCall(item));
1637
1974
  continue;
1638
1975
  }
1639
1976
  if (event.type === "response.completed" || event.type === "response.incomplete") {
@@ -1654,7 +1991,7 @@ var OpenAIResponsesModel = class {
1654
1991
  } catch (thrown) {
1655
1992
  throw this.logAndWrap(thrown);
1656
1993
  }
1657
- for (const call of attachReasoningReplay(toolCalls, this.name, reasoningItems)) yield {
1994
+ for (const call of attachReasoningReplay(toolCalls, this.name, pickTurnReasoning(outputItems))) yield {
1658
1995
  type: "tool-call",
1659
1996
  id: call.id,
1660
1997
  name: call.name,
@@ -1692,11 +2029,11 @@ var OpenAIResponsesModel = class {
1692
2029
  */
1693
2030
  buildRequest(messages, options) {
1694
2031
  const { instructions, input } = toResponsesInput(messages, this.name);
1695
- const tools = toResponsesTools(options?.tools);
2032
+ const tools = this.buildTools(options);
1696
2033
  const toolChoice = tools ? toResponsesToolChoice(options?.toolChoice) : void 0;
1697
2034
  const maxTokens = options?.maxTokens ?? this.config.maxTokens;
1698
2035
  const temperature = options?.temperature ?? this.config.temperature;
1699
- return {
2036
+ return mergeProviderBody({
1700
2037
  model: this.name,
1701
2038
  ...instructions !== void 0 ? { instructions } : {},
1702
2039
  input,
@@ -1709,7 +2046,24 @@ var OpenAIResponsesModel = class {
1709
2046
  ...this.buildTextFormat(options?.responseSchema),
1710
2047
  ...this.buildReasoningParams(options?.reasoning),
1711
2048
  ...buildPromptCacheParams(this.provider, this.config)
1712
- };
2049
+ }, options);
2050
+ }
2051
+ /**
2052
+ * The request `tools`: function tools (flat, `strict: false`), then the
2053
+ * `ai.providerTool({ provider: "openai" })` definitions verbatim — the
2054
+ * Responses built-in tools (`web_search`, `file_search`,
2055
+ * `code_interpreter`, `image_generation`, `mcp`, …). Other providers'
2056
+ * definitions are skipped with a warning. `undefined` when there are none.
2057
+ */
2058
+ buildTools(options) {
2059
+ const { tools, definitions } = (0, _warlock_js_ai.selectProviderTools)(options, {
2060
+ accepts: [this.provider],
2061
+ module: LOG_MODULE$2,
2062
+ model: this.name,
2063
+ logger: this.logger
2064
+ });
2065
+ const mapped = [...toResponsesTools(tools) ?? [], ...definitions];
2066
+ return mapped.length > 0 ? mapped : void 0;
1713
2067
  }
1714
2068
  /**
1715
2069
  * Structured output on Responses lives in `text.format`, and `name` /
@@ -1746,12 +2100,8 @@ var OpenAIResponsesModel = class {
1746
2100
  readOutput(output) {
1747
2101
  let content = "";
1748
2102
  const toolCalls = [];
1749
- const reasoningItems = [];
1750
2103
  for (const item of output) {
1751
- if (item.type === "reasoning") {
1752
- reasoningItems.push(item);
1753
- continue;
1754
- }
2104
+ this.assertServerRunItem(item);
1755
2105
  if (item.type === "message") {
1756
2106
  for (const part of item.content) content += part.type === "refusal" ? part.refusal : part.text;
1757
2107
  continue;
@@ -1760,9 +2110,24 @@ var OpenAIResponsesModel = class {
1760
2110
  }
1761
2111
  return {
1762
2112
  content,
1763
- toolCalls: toolCalls.length > 0 ? attachReasoningReplay(toolCalls, this.name, reasoningItems) : void 0
2113
+ toolCalls: toolCalls.length > 0 ? attachReasoningReplay(toolCalls, this.name, pickTurnReasoning(output)) : void 0
1764
2114
  };
1765
2115
  }
2116
+ /**
2117
+ * Fail closed on a built-in tool call that YOUR code would have to run
2118
+ * (`shell_call`, `apply_patch_call`, `computer_call`, …, from a
2119
+ * provider-defined client tool). These items have their own output item
2120
+ * types that this adapter does not map, so the turn would otherwise end
2121
+ * silently with the call dropped. Hosted (server-run) tool items pass.
2122
+ */
2123
+ assertServerRunItem(item) {
2124
+ if (!CLIENT_RUN_ITEM_TYPES.has(item.type)) return;
2125
+ throw new _warlock_js_ai.InvalidRequestError(`OpenAI returned a "${item.type}" item: that built-in tool is executed by the caller, which the Responses adapter does not support. Use OpenAI's server-run built-in tools (web_search, file_search, code_interpreter, image_generation, mcp without approval) with ai.providerTool(), or a function tool from ai.tool().`, { context: {
2126
+ provider: this.provider,
2127
+ model: this.name,
2128
+ itemType: item.type
2129
+ } });
2130
+ }
1766
2131
  /** One completed `function_call` item as a neutral tool call (`call_id` -> `id`). */
1767
2132
  toToolCall(item) {
1768
2133
  return {
@@ -1791,7 +2156,7 @@ var OpenAIResponsesModel = class {
1791
2156
  cachedTokens: raw.input_tokens_details?.cached_tokens,
1792
2157
  cacheWriteTokens: raw.input_tokens_details?.cache_write_tokens,
1793
2158
  reasoningTokens: raw.output_tokens_details?.reasoning_tokens
1794
- });
2159
+ }, this.pricing);
1795
2160
  }
1796
2161
  /** Wrap (idempotently), log and return a failure for the caller to throw. */
1797
2162
  logAndWrap(thrown) {
@@ -1805,7 +2170,7 @@ var OpenAIResponsesModel = class {
1805
2170
  };
1806
2171
 
1807
2172
  //#endregion
1808
- //#region ../ai-openai/src/speech.ts
2173
+ //#region ../../packages/ai-openai/src/speech.ts
1809
2174
  const LOG_MODULE$1 = "ai.openai";
1810
2175
  /** Map a neutral output container hint to its IANA audio media type. */
1811
2176
  function audioMediaType(format) {
@@ -1883,7 +2248,7 @@ var OpenAISpeechModel = class {
1883
2248
  };
1884
2249
 
1885
2250
  //#endregion
1886
- //#region ../ai-openai/src/transcription.ts
2251
+ //#region ../../packages/ai-openai/src/transcription.ts
1887
2252
  const LOG_MODULE = "ai.openai";
1888
2253
  /**
1889
2254
  * OpenAI-backed implementation of `TranscriptionModelContract`
@@ -1963,7 +2328,7 @@ var OpenAITranscriptionModel = class {
1963
2328
  };
1964
2329
 
1965
2330
  //#endregion
1966
- //#region ../ai-openai/src/sdk.ts
2331
+ //#region ../../packages/ai-openai/src/sdk.ts
1967
2332
  /**
1968
2333
  * OpenAI-backed implementation of `SDKAdapterContract`.
1969
2334
  *
@@ -2014,8 +2379,9 @@ var OpenAISDK = class {
2014
2379
  * coming from the same upstream.
2015
2380
  *
2016
2381
  * Pricing resolution: per-model `config.pricing` wins; otherwise the
2017
- * SDK-level registry entry keyed by `config.name`; otherwise
2018
- * `undefined` (no cost computed).
2382
+ * SDK-level registry entry keyed by `config.name`; otherwise, when the
2383
+ * provider label is `"openai"`, the built-in list price
2384
+ * (`OPENAI_MODEL_PRICING`); otherwise `undefined` (no cost computed).
2019
2385
  *
2020
2386
  * `config.api` selects the wire API. Omitted or `"chat"` builds the Chat
2021
2387
  * Completions `OpenAIModel`. `"responses"` builds `OpenAIResponsesModel`
@@ -2036,7 +2402,7 @@ var OpenAISDK = class {
2036
2402
  model: config.name,
2037
2403
  api
2038
2404
  } });
2039
- const resolvedPricing = config.pricing ?? this.pricing?.[config.name];
2405
+ const resolvedPricing = config.pricing ?? this.pricing?.[config.name] ?? (this.provider === "openai" ? openAIPricingFor(config.name) : void 0);
2040
2406
  const resolvedConfig = resolvedPricing === config.pricing ? config : {
2041
2407
  ...config,
2042
2408
  pricing: resolvedPricing
@@ -2123,9 +2489,11 @@ var OpenAISDK = class {
2123
2489
  };
2124
2490
 
2125
2491
  //#endregion
2492
+ exports.OPENAI_MODEL_PRICING = OPENAI_MODEL_PRICING;
2126
2493
  exports.OpenAIEmbedder = OpenAIEmbedder;
2127
2494
  exports.OpenAIImageModel = OpenAIImageModel;
2128
2495
  exports.OpenAISDK = OpenAISDK;
2129
2496
  exports.OpenAISpeechModel = OpenAISpeechModel;
2130
2497
  exports.OpenAITranscriptionModel = OpenAITranscriptionModel;
2498
+ exports.openAIPricingFor = openAIPricingFor;
2131
2499
  //# sourceMappingURL=index.cjs.map