@warlock.js/ai-openai 5.30.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.
- package/CHANGELOG.md +24 -1
- package/README.md +3 -3
- package/cjs/index.cjs +416 -48
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +5 -3
- package/esm/config.type.d.mts.map +1 -1
- package/esm/embedder.d.mts.map +1 -1
- package/esm/embedder.mjs.map +1 -1
- package/esm/index.d.mts +3 -1
- package/esm/index.mjs +2 -1
- package/esm/known-pricing.d.mts +35 -0
- package/esm/known-pricing.d.mts.map +1 -0
- package/esm/known-pricing.mjs +117 -0
- package/esm/known-pricing.mjs.map +1 -0
- package/esm/model.mjs +75 -23
- package/esm/model.mjs.map +1 -1
- package/esm/provider-options.d.mts +8 -0
- package/esm/provider-options.d.mts.map +1 -0
- package/esm/provider-options.mjs +49 -0
- package/esm/provider-options.mjs.map +1 -0
- package/esm/responses-model.mjs +116 -17
- package/esm/responses-model.mjs.map +1 -1
- package/esm/sdk.d.mts +3 -2
- package/esm/sdk.d.mts.map +1 -1
- package/esm/sdk.mjs +5 -3
- package/esm/sdk.mjs.map +1 -1
- package/esm/transcription.mjs.map +1 -1
- package/esm/utils/build-usage.mjs +14 -2
- package/esm/utils/build-usage.mjs.map +1 -1
- package/esm/utils/index.mjs +1 -1
- package/esm/utils/map-responses-status.mjs.map +1 -1
- package/esm/utils/prompt-cache-params.mjs +25 -6
- package/esm/utils/prompt-cache-params.mjs.map +1 -1
- package/esm/utils/reasoning-replay.mjs +23 -1
- package/esm/utils/reasoning-replay.mjs.map +1 -1
- package/esm/utils/structured-output.mjs.map +1 -1
- package/esm/utils/to-openai-messages.mjs.map +1 -1
- package/esm/utils/to-openai-tools.mjs.map +1 -1
- package/esm/utils/wrap-openai-error.mjs +6 -1
- package/esm/utils/wrap-openai-error.mjs.map +1 -1
- package/llms-full.txt +128 -37
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/skills/setup-openai/SKILL.md +128 -37
package/cjs/index.cjs
CHANGED
|
@@ -31,6 +31,121 @@ 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 ../../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
|
|
34
149
|
//#region ../../packages/ai-openai/src/utils/build-usage.ts
|
|
35
150
|
/**
|
|
36
151
|
* Build the neutral `Usage` from extracted counts. The optional detail
|
|
@@ -38,17 +153,27 @@ let _warlock_js_logger = require("@warlock.js/logger");
|
|
|
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
|
-
|
|
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
|
|
@@ -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
|
|
@@ -271,15 +401,34 @@ function mapFinishReason(raw) {
|
|
|
271
401
|
//#endregion
|
|
272
402
|
//#region ../../packages/ai-openai/src/utils/prompt-cache-params.ts
|
|
273
403
|
/**
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
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
|
+
]);
|
|
421
|
+
/**
|
|
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
|
|
|
@@ -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
|
|
@@ -1065,6 +1236,51 @@ var OpenAIImageModel = class {
|
|
|
1065
1236
|
}
|
|
1066
1237
|
};
|
|
1067
1238
|
|
|
1239
|
+
//#endregion
|
|
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
|
+
|
|
1068
1284
|
//#endregion
|
|
1069
1285
|
//#region ../../packages/ai-openai/src/known-reasoning-models.ts
|
|
1070
1286
|
/**
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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
|
|
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
|
-
|
|
1636
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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) {
|
|
@@ -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
|
-
* `
|
|
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
|