@openclaw/ai 2026.7.2-beta.4 → 2026.7.2-beta.6
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/dist/{anthropic-CrUDIBpM.mjs → anthropic-CH4UUnZr.mjs} +26 -441
- package/dist/{anthropic-BoTnz8cv.d.mts → anthropic-SrGtwsJu.d.mts} +27 -1
- package/dist/anthropic-usage-DWU-x8MI.mjs +459 -0
- package/dist/{api-registry-BMphkFf1.d.mts → api-registry-DlMgPR39.d.mts} +1 -1
- package/dist/{azure-openai-responses-CbrpVeEv.mjs → azure-openai-responses-CImcwB83.mjs} +4 -4
- package/dist/cache-retention-0x979a5V.mjs +12 -0
- package/dist/deferred-event-buffer-DAvyP7qA.mjs +19 -0
- package/dist/error-coercion-DgxlWC0n.mjs +15 -0
- package/dist/{event-stream-Douf9dob.d.mts → event-stream-YjaPW20U.d.mts} +1 -1
- package/dist/event-stream.d.mts +1 -1
- package/dist/{github-copilot-headers-BsH5cqGj.mjs → github-copilot-headers-NCJtz9i0.mjs} +1 -12
- package/dist/{google-Cs62KA7t.mjs → google-CtSg0iTS.mjs} +3 -3
- package/dist/{google-shared-CMLI-tCZ.mjs → google-shared-DNBz5rcD.mjs} +7 -4
- package/dist/{google-vertex-B4SD3U0f.mjs → google-vertex-31f1uS9L.mjs} +3 -3
- package/dist/{host-WvWBo4h8.d.mts → host-B9GUmcra.d.mts} +2 -2
- package/dist/host-Dog2WQiR.mjs +369 -0
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +3 -3
- package/dist/internal/anthropic.d.mts +16 -62
- package/dist/internal/anthropic.mjs +5 -4
- package/dist/internal/openai.d.mts +260 -2
- package/dist/internal/openai.mjs +7 -5
- package/dist/internal/runtime.d.mts +8 -32
- package/dist/internal/runtime.mjs +7 -6
- package/dist/internal/shared.d.mts +14 -9
- package/dist/internal/shared.mjs +6 -2
- package/dist/{llm-request-activity-CehVkZP-.mjs → llm-request-activity-BjtkplhG.mjs} +1 -19
- package/dist/{mistral-D5Ps7Led.mjs → mistral-CWmpvWYh.mjs} +7 -4
- package/dist/number-coercion-DvG7SNMg.mjs +129 -0
- package/dist/{openai-chatgpt-responses-h0o5yV8Y.mjs → openai-chatgpt-responses-B84Ibtrd.mjs} +33 -29
- package/dist/{openai-completions-exO8NFQf.mjs → openai-completions-DsOxhOD1.mjs} +28 -482
- package/dist/openai-completions-compat-DBWjXoMZ.d.mts +43 -0
- package/dist/openai-prompt-cache-2uo_1OR1.d.mts +7 -0
- package/dist/openai-prompt-cache-mZTCdRPo.mjs +12 -0
- package/dist/openai-reasoning-compat-YgeLncHw.mjs +396 -0
- package/dist/{openai-responses-BHpmtUKo.mjs → openai-responses-BT7A3sLu.mjs} +7 -5
- package/dist/openai-responses-shared-pXl6Wd8S.mjs +392 -0
- package/dist/{openai-responses-shared-CzCurmY1.mjs → openai-responses-stream-internal-Cw5txaGW.mjs} +1609 -1037
- package/dist/{openai-tool-projection-ITOU9bG1.mjs → openai-tool-projection-OhX64DoP.mjs} +26 -11
- package/dist/prompt-cache-stability-Cwcjv_fx.d.mts +13 -0
- package/dist/{provider-error-C4VvV_3t.mjs → provider-error-CAEvRjry.mjs} +8 -2
- package/dist/provider-options-D8bB3z9b.d.mts +144 -0
- package/dist/providers.d.mts +3 -2
- package/dist/providers.mjs +10 -9
- package/dist/reasoning-tag-text-partitioner-CGDyLWUR.mjs +12209 -0
- package/dist/simple-options-9lhRrN73.mjs +50 -0
- package/dist/{src-CXno1H5g.mjs → src-QkygScBs.mjs} +23 -4
- package/dist/stream-first-event-timeout-BBys9hSb.mjs +86 -0
- package/dist/stream-first-event-timeout-DvDeSucC.d.mts +29 -0
- package/dist/tls-certificate-errors-DXSpluKI.mjs +93 -0
- package/dist/tool-result-text-CTpIRbYd.mjs +225 -0
- package/dist/transform-messages-C8mBqZxF.mjs +2 -0
- package/dist/transport-stream-shared-D81p90xq.mjs +297 -0
- package/dist/transports.d.mts +115 -132
- package/dist/transports.mjs +400 -1991
- package/dist/{types-AFwwWium.d.mts → types-bzp5k29J.d.mts} +7 -0
- package/dist/types.d.mts +5 -5
- package/dist/types.mjs +2 -2
- package/dist/{validation-sxvxC8J-.d.mts → validation-B-j7cOYp.d.mts} +1 -1
- package/dist/validation.d.mts +1 -1
- package/package.json +4 -5
- package/dist/host-XYGZcgO8.mjs +0 -98
- package/dist/model-utils-1GiZ2_rr.mjs +0 -64
- package/dist/openai-CoGicoDt.d.mts +0 -332
- package/dist/reasoning-tag-text-partitioner-BlVe67g6.mjs +0 -400
- package/dist/stream-first-event-timeout-DP4xEyBY.mjs +0 -150
- package/dist/system-prompt-cache-boundary-CbHeV4_l.mjs +0 -526
- package/npm-shrinkwrap.json +0 -638
package/dist/{openai-responses-shared-CzCurmY1.mjs → openai-responses-stream-internal-Cw5txaGW.mjs}
RENAMED
|
@@ -1,13 +1,51 @@
|
|
|
1
|
-
import { n as getAiTransportHost } from "./host-
|
|
2
|
-
import {
|
|
3
|
-
import { t as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { g as isRecord, m as normalizeOptionalString, n as getAiTransportHost, p as normalizeLowercaseStringOrEmpty } from "./host-Dog2WQiR.mjs";
|
|
2
|
+
import { n as clampOpenAIPromptCacheKey } from "./openai-prompt-cache-mZTCdRPo.mjs";
|
|
3
|
+
import { a as isImageWithMediaPayload, d as stripSystemPromptCacheBoundary, o as truncateUtf16Safe, r as extractToolResultText, t as describeToolResultMediaPlaceholder } from "./tool-result-text-CTpIRbYd.mjs";
|
|
4
|
+
import { u as transformTransportMessages } from "./openai-tool-projection-OhX64DoP.mjs";
|
|
5
|
+
import { c as calculateCost } from "./number-coercion-DvG7SNMg.mjs";
|
|
6
6
|
import { n as parseStreamingJson } from "./json-parse-BvXNt1-7.mjs";
|
|
7
|
+
import { b as redactIdentifier, d as transportAbortError, l as sanitizeNonEmptyTransportPayloadText, u as sanitizeTransportPayloadText, x as redactSensitiveText } from "./transport-stream-shared-D81p90xq.mjs";
|
|
8
|
+
import { a as withFirstStreamEventTimeout } from "./stream-first-event-timeout-BBys9hSb.mjs";
|
|
7
9
|
import { t as shortHash } from "./hash-CHgqbJmD.mjs";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
//#region packages/ai/src/transports/model-transport-debug.ts
|
|
12
|
+
function normalizeEnv(value) {
|
|
13
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
14
|
+
}
|
|
15
|
+
function isTruthyEnv(value) {
|
|
16
|
+
const normalized = normalizeEnv(value);
|
|
17
|
+
return normalized.length > 0 && normalized !== "0" && normalized !== "false" && normalized !== "off" && normalized !== "no";
|
|
18
|
+
}
|
|
19
|
+
/** Resolves model payload debug verbosity from `OPENCLAW_DEBUG_MODEL_PAYLOAD`. */
|
|
20
|
+
function resolveModelPayloadDebugMode(env = process.env) {
|
|
21
|
+
const normalized = normalizeEnv(env.OPENCLAW_DEBUG_MODEL_PAYLOAD);
|
|
22
|
+
if (normalized === "tools" || normalized === "full-redacted") return normalized;
|
|
23
|
+
if (normalized === "summary") return "summary";
|
|
24
|
+
return "off";
|
|
25
|
+
}
|
|
26
|
+
/** Resolves SSE stream debug verbosity from `OPENCLAW_DEBUG_SSE`. */
|
|
27
|
+
function resolveModelSseDebugMode(env = process.env) {
|
|
28
|
+
const normalized = normalizeEnv(env.OPENCLAW_DEBUG_SSE);
|
|
29
|
+
if (normalized === "peek") return "peek";
|
|
30
|
+
if (normalized === "events" || isTruthyEnv(normalized)) return "events";
|
|
31
|
+
return "off";
|
|
32
|
+
}
|
|
33
|
+
/** Returns whether any model transport debug channel is enabled. */
|
|
34
|
+
function isModelTransportDebugEnabled(env = process.env) {
|
|
35
|
+
return isTruthyEnv(env.OPENCLAW_DEBUG_MODEL_TRANSPORT) || resolveModelPayloadDebugMode(env) !== "off" || resolveModelSseDebugMode(env) !== "off" || isTruthyEnv(env.OPENCLAW_DEBUG_CODE_MODE);
|
|
36
|
+
}
|
|
37
|
+
function isModelFetchMetadataMessage(message) {
|
|
38
|
+
return message.startsWith("[model-fetch]");
|
|
39
|
+
}
|
|
40
|
+
/** Emits model-fetch metadata at info level by default; other diagnostics require debug env. */
|
|
41
|
+
function emitModelTransportDebug(log, message) {
|
|
42
|
+
if (isModelFetchMetadataMessage(message) || isModelTransportDebugEnabled()) {
|
|
43
|
+
log.info(message);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
log.debug(message);
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
11
49
|
//#region packages/normalization-core/src/string-normalization.ts
|
|
12
50
|
/** Coerces entries to strings, trims them, and drops empty results. */
|
|
13
51
|
function normalizeStringEntries(list) {
|
|
@@ -22,6 +60,159 @@ function uniqueStrings(values) {
|
|
|
22
60
|
return uniqueValues(values);
|
|
23
61
|
}
|
|
24
62
|
//#endregion
|
|
63
|
+
//#region packages/ai/src/providers/openai-reasoning-effort.ts
|
|
64
|
+
/**
|
|
65
|
+
* OpenAI-compatible reasoning-effort normalization. Different GPT families
|
|
66
|
+
* expose different accepted effort enums, so callers map requested values here
|
|
67
|
+
* before constructing provider payloads.
|
|
68
|
+
*/
|
|
69
|
+
const GPT_5_REASONING_EFFORTS = [
|
|
70
|
+
"minimal",
|
|
71
|
+
"low",
|
|
72
|
+
"medium",
|
|
73
|
+
"high"
|
|
74
|
+
];
|
|
75
|
+
const GPT_51_REASONING_EFFORTS = [
|
|
76
|
+
"none",
|
|
77
|
+
"low",
|
|
78
|
+
"medium",
|
|
79
|
+
"high"
|
|
80
|
+
];
|
|
81
|
+
const GPT_52_REASONING_EFFORTS = [
|
|
82
|
+
"none",
|
|
83
|
+
"low",
|
|
84
|
+
"medium",
|
|
85
|
+
"high",
|
|
86
|
+
"xhigh"
|
|
87
|
+
];
|
|
88
|
+
const GPT_56_REASONING_EFFORTS = [
|
|
89
|
+
"none",
|
|
90
|
+
"low",
|
|
91
|
+
"medium",
|
|
92
|
+
"high",
|
|
93
|
+
"xhigh",
|
|
94
|
+
"max"
|
|
95
|
+
];
|
|
96
|
+
const GPT_CODEX_REASONING_EFFORTS = [
|
|
97
|
+
"low",
|
|
98
|
+
"medium",
|
|
99
|
+
"high",
|
|
100
|
+
"xhigh"
|
|
101
|
+
];
|
|
102
|
+
const GPT_PRO_REASONING_EFFORTS = [
|
|
103
|
+
"medium",
|
|
104
|
+
"high",
|
|
105
|
+
"xhigh"
|
|
106
|
+
];
|
|
107
|
+
const GPT_5_PRO_REASONING_EFFORTS = ["high"];
|
|
108
|
+
const GPT_51_CODEX_MAX_REASONING_EFFORTS = [
|
|
109
|
+
"none",
|
|
110
|
+
"medium",
|
|
111
|
+
"high",
|
|
112
|
+
"xhigh"
|
|
113
|
+
];
|
|
114
|
+
const GPT_51_CODEX_MINI_REASONING_EFFORTS = ["medium"];
|
|
115
|
+
const GENERIC_REASONING_EFFORTS = [
|
|
116
|
+
"low",
|
|
117
|
+
"medium",
|
|
118
|
+
"high"
|
|
119
|
+
];
|
|
120
|
+
const CANONICAL_REASONING_EFFORTS = /* @__PURE__ */ new Set([
|
|
121
|
+
"none",
|
|
122
|
+
"minimal",
|
|
123
|
+
"low",
|
|
124
|
+
"medium",
|
|
125
|
+
"high",
|
|
126
|
+
"xhigh",
|
|
127
|
+
"max",
|
|
128
|
+
"off"
|
|
129
|
+
]);
|
|
130
|
+
function normalizeModelId(id) {
|
|
131
|
+
return normalizeLowercaseStringOrEmpty(id ?? "").replace(/-\d{4}-\d{2}-\d{2}$/u, "");
|
|
132
|
+
}
|
|
133
|
+
/** Return whether a model is the GPT-5.4 mini family. */
|
|
134
|
+
function isOpenAIGpt54MiniModel(model) {
|
|
135
|
+
const id = normalizeModelId(typeof model.id === "string" ? model.id : void 0);
|
|
136
|
+
return /^gpt-5\.4-mini(?:-|$)/u.test(id);
|
|
137
|
+
}
|
|
138
|
+
/** Return whether a model is the GPT-5.5 family. */
|
|
139
|
+
function isOpenAIGpt55Model(model) {
|
|
140
|
+
const id = normalizeModelId(typeof model.id === "string" ? model.id : void 0);
|
|
141
|
+
const name = normalizeModelId(typeof model.name === "string" ? model.name : void 0);
|
|
142
|
+
return /^gpt-5\.5(?:-|$)/u.test(id) || /^gpt-5\.5(?:\s|\(|-|$)/u.test(name);
|
|
143
|
+
}
|
|
144
|
+
/** Return whether a model is the GPT-5.6 family. */
|
|
145
|
+
function isOpenAIGpt56Model(model) {
|
|
146
|
+
const id = normalizeModelId(typeof model.id === "string" ? model.id : void 0);
|
|
147
|
+
const name = normalizeModelId(typeof model.name === "string" ? model.name : void 0);
|
|
148
|
+
return /^gpt-5\.6(?:-|$)/u.test(id) || /^gpt-5\.6(?:\s|\(|-|$)/u.test(name);
|
|
149
|
+
}
|
|
150
|
+
/** Normalize user-facing reasoning effort names to API effort names. */
|
|
151
|
+
function normalizeOpenAIReasoningEffort(effort) {
|
|
152
|
+
const trimmed = effort.trim();
|
|
153
|
+
const folded = trimmed.toLowerCase();
|
|
154
|
+
return CANONICAL_REASONING_EFFORTS.has(folded) ? folded : trimmed;
|
|
155
|
+
}
|
|
156
|
+
function readCompatReasoningEfforts(compat) {
|
|
157
|
+
if (!compat || typeof compat !== "object") return;
|
|
158
|
+
if (compat.supportsReasoningEffort === false) return [];
|
|
159
|
+
const raw = compat.supportedReasoningEfforts;
|
|
160
|
+
if (!Array.isArray(raw)) return;
|
|
161
|
+
const supported = uniqueStrings(normalizeStringEntries(raw.filter((value) => typeof value === "string")));
|
|
162
|
+
return supported.length > 0 ? supported : void 0;
|
|
163
|
+
}
|
|
164
|
+
function isDisabledReasoningEffort(effort) {
|
|
165
|
+
return effort === "none" || effort === "off";
|
|
166
|
+
}
|
|
167
|
+
/** Resolve the reasoning efforts accepted by a specific OpenAI-compatible model. */
|
|
168
|
+
function resolveOpenAISupportedReasoningEfforts(model) {
|
|
169
|
+
const compatEfforts = readCompatReasoningEfforts(model.compat);
|
|
170
|
+
if (compatEfforts) return compatEfforts;
|
|
171
|
+
const id = normalizeModelId(typeof model.id === "string" ? model.id : void 0);
|
|
172
|
+
if (/^gpt-5\.6(?:-|$)/u.test(id)) return GPT_56_REASONING_EFFORTS;
|
|
173
|
+
if (id === "gpt-5.1-codex-mini") return GPT_51_CODEX_MINI_REASONING_EFFORTS;
|
|
174
|
+
if (id === "gpt-5.1-codex-max") return GPT_51_CODEX_MAX_REASONING_EFFORTS;
|
|
175
|
+
if (/^gpt-5(?:\.\d+)?-codex(?:-|$)/u.test(id)) return GPT_CODEX_REASONING_EFFORTS;
|
|
176
|
+
if (id === "gpt-5-pro") return GPT_5_PRO_REASONING_EFFORTS;
|
|
177
|
+
if (/^gpt-5\.[2-9](?:\.\d+)?-pro(?:-|$)/u.test(id)) return GPT_PRO_REASONING_EFFORTS;
|
|
178
|
+
if (/^gpt-5\.[2-9](?:\.\d+)?(?:-|$)/u.test(id)) return GPT_52_REASONING_EFFORTS;
|
|
179
|
+
if (/^gpt-5\.1(?:-|$)/u.test(id)) return GPT_51_REASONING_EFFORTS;
|
|
180
|
+
if (/^gpt-5(?:-|$)/u.test(id)) return GPT_5_REASONING_EFFORTS;
|
|
181
|
+
return GENERIC_REASONING_EFFORTS;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Return whether a model accepts the temperature parameter. The GPT-5.6
|
|
185
|
+
* family rejects it with a 400; catalog compat can override per model.
|
|
186
|
+
*/
|
|
187
|
+
function supportsOpenAITemperature(model) {
|
|
188
|
+
const compat = model.compat;
|
|
189
|
+
if (compat && typeof compat === "object") {
|
|
190
|
+
const declared = compat.supportsTemperature;
|
|
191
|
+
if (typeof declared === "boolean") return declared;
|
|
192
|
+
}
|
|
193
|
+
const id = normalizeModelId(typeof model.id === "string" ? model.id : void 0);
|
|
194
|
+
return !/^gpt-5\.6(?:-|$)/u.test(id);
|
|
195
|
+
}
|
|
196
|
+
/** Return whether a model accepts a requested reasoning effort. */
|
|
197
|
+
function supportsOpenAIReasoningEffort(model, effort) {
|
|
198
|
+
return resolveOpenAISupportedReasoningEfforts(model).includes(normalizeOpenAIReasoningEffort(effort));
|
|
199
|
+
}
|
|
200
|
+
/** Resolve a requested reasoning effort to the closest value supported by the model. */
|
|
201
|
+
function resolveOpenAIReasoningEffortForModel(params) {
|
|
202
|
+
const requested = normalizeOpenAIReasoningEffort(params.effort);
|
|
203
|
+
const mapped = params.fallbackMap?.[requested] ?? (params.fallbackMap && CANONICAL_REASONING_EFFORTS.has(requested) ? Object.entries(params.fallbackMap).find(([effort]) => normalizeOpenAIReasoningEffort(effort) === requested)?.[1] : void 0);
|
|
204
|
+
const normalized = mapped === void 0 ? requested : mapped.trim();
|
|
205
|
+
const supported = resolveOpenAISupportedReasoningEfforts(params.model);
|
|
206
|
+
if (supported.includes(normalized)) return normalized;
|
|
207
|
+
if (requested === "off" && supported.includes("none")) return "none";
|
|
208
|
+
if (isDisabledReasoningEffort(requested) || isDisabledReasoningEffort(normalized)) return;
|
|
209
|
+
if (requested === "minimal" && supported.includes("low")) return "low";
|
|
210
|
+
if ((requested === "minimal" || requested === "low") && supported.includes("medium")) return "medium";
|
|
211
|
+
if (requested === "xhigh" && supported.includes("high")) return "high";
|
|
212
|
+
if (requested === "max" && supported.includes("xhigh")) return "xhigh";
|
|
213
|
+
return supported.find((effort) => !isDisabledReasoningEffort(normalizeOpenAIReasoningEffort(effort)));
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
25
216
|
//#region packages/ai/src/providers/clean-for-gemini.ts
|
|
26
217
|
const GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
27
218
|
"patternProperties",
|
|
@@ -293,7 +484,127 @@ function cleanSchemaForGemini(schema) {
|
|
|
293
484
|
return cleanSchemaForGeminiWithDefs(schema, extendSchemaDefs$1(void 0, schema), void 0);
|
|
294
485
|
}
|
|
295
486
|
//#endregion
|
|
487
|
+
//#region packages/ai/src/providers/clean-for-llamacpp-gbnf.ts
|
|
488
|
+
/** llama.cpp rejects grammar repetitions whose expanded rule count reaches 2000. */
|
|
489
|
+
const LLAMACPP_GBNF_MAX_REPETITION_THRESHOLD = 2e3;
|
|
490
|
+
const SCHEMA_MAP_KEYS$2 = /* @__PURE__ */ new Set([
|
|
491
|
+
"$defs",
|
|
492
|
+
"definitions",
|
|
493
|
+
"dependentSchemas",
|
|
494
|
+
"patternProperties",
|
|
495
|
+
"properties"
|
|
496
|
+
]);
|
|
497
|
+
const SCHEMA_CHILD_KEYS = /* @__PURE__ */ new Set([
|
|
498
|
+
"additionalItems",
|
|
499
|
+
"additionalProperties",
|
|
500
|
+
"allOf",
|
|
501
|
+
"anyOf",
|
|
502
|
+
"contains",
|
|
503
|
+
"else",
|
|
504
|
+
"if",
|
|
505
|
+
"items",
|
|
506
|
+
"not",
|
|
507
|
+
"oneOf",
|
|
508
|
+
"prefixItems",
|
|
509
|
+
"propertyNames",
|
|
510
|
+
"then",
|
|
511
|
+
"unevaluatedItems",
|
|
512
|
+
"unevaluatedProperties"
|
|
513
|
+
]);
|
|
514
|
+
function isSchemaRecord(value) {
|
|
515
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
516
|
+
}
|
|
517
|
+
function cleanSchemaNode(node) {
|
|
518
|
+
if (Array.isArray(node)) {
|
|
519
|
+
let changed = false;
|
|
520
|
+
const entries = node.map((entry) => {
|
|
521
|
+
const next = cleanSchemaNode(entry);
|
|
522
|
+
changed ||= next !== entry;
|
|
523
|
+
return next;
|
|
524
|
+
});
|
|
525
|
+
return changed ? entries : node;
|
|
526
|
+
}
|
|
527
|
+
if (!isSchemaRecord(node)) return node;
|
|
528
|
+
let changed = false;
|
|
529
|
+
const cleaned = {};
|
|
530
|
+
for (const [key, value] of Object.entries(node)) {
|
|
531
|
+
if (key === "pattern") {
|
|
532
|
+
changed = true;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (key === "maxLength" && typeof value === "number" && value >= 2e3) {
|
|
536
|
+
changed = true;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
let next = value;
|
|
540
|
+
if (SCHEMA_MAP_KEYS$2.has(key) && isSchemaRecord(value)) {
|
|
541
|
+
let mapChanged = false;
|
|
542
|
+
next = Object.fromEntries(Object.entries(value).map(([childKey, childValue]) => {
|
|
543
|
+
const cleanedChild = cleanSchemaNode(childValue);
|
|
544
|
+
mapChanged ||= cleanedChild !== childValue;
|
|
545
|
+
return [childKey, cleanedChild];
|
|
546
|
+
}));
|
|
547
|
+
if (!mapChanged) next = value;
|
|
548
|
+
} else if (SCHEMA_CHILD_KEYS.has(key)) next = cleanSchemaNode(value);
|
|
549
|
+
cleaned[key] = next;
|
|
550
|
+
changed ||= next !== value;
|
|
551
|
+
}
|
|
552
|
+
return changed ? cleaned : node;
|
|
553
|
+
}
|
|
554
|
+
function collectSchemaViolations(node, path, violations) {
|
|
555
|
+
if (Array.isArray(node)) {
|
|
556
|
+
node.forEach((entry, index) => collectSchemaViolations(entry, `${path}[${index}]`, violations));
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (!isSchemaRecord(node)) return;
|
|
560
|
+
if ("pattern" in node) violations.push(`${path}.pattern`);
|
|
561
|
+
if (typeof node.maxLength === "number" && node.maxLength >= 2e3) violations.push(`${path}.maxLength`);
|
|
562
|
+
for (const [key, value] of Object.entries(node)) if (SCHEMA_MAP_KEYS$2.has(key) && isSchemaRecord(value)) for (const [childKey, childValue] of Object.entries(value)) collectSchemaViolations(childValue, `${path}.${key}.${childKey}`, violations);
|
|
563
|
+
else if (SCHEMA_CHILD_KEYS.has(key)) collectSchemaViolations(value, `${path}.${key}`, violations);
|
|
564
|
+
}
|
|
565
|
+
/** Removes JSON Schema constraints that llama.cpp cannot compile into GBNF. */
|
|
566
|
+
function cleanSchemaForLlamacppGbnf(schema) {
|
|
567
|
+
return cleanSchemaNode(schema);
|
|
568
|
+
}
|
|
569
|
+
/** Reports schema paths that llama.cpp cannot compile into GBNF. */
|
|
570
|
+
function findLlamacppGbnfSchemaViolations(schema, path) {
|
|
571
|
+
const violations = [];
|
|
572
|
+
collectSchemaViolations(schema, path, violations);
|
|
573
|
+
return violations;
|
|
574
|
+
}
|
|
575
|
+
//#endregion
|
|
296
576
|
//#region packages/ai/src/providers/schema-keyword-strip.ts
|
|
577
|
+
const SCHEMA_MAP_KEYS$1 = /* @__PURE__ */ new Set([
|
|
578
|
+
"$defs",
|
|
579
|
+
"definitions",
|
|
580
|
+
"dependentSchemas",
|
|
581
|
+
"dependencies",
|
|
582
|
+
"patternProperties",
|
|
583
|
+
"properties"
|
|
584
|
+
]);
|
|
585
|
+
/** Containers whose value is a single nested schema. */
|
|
586
|
+
const SCHEMA_OBJECT_KEYS$1 = /* @__PURE__ */ new Set([
|
|
587
|
+
"additionalItems",
|
|
588
|
+
"additionalProperties",
|
|
589
|
+
"contains",
|
|
590
|
+
"contentSchema",
|
|
591
|
+
"else",
|
|
592
|
+
"if",
|
|
593
|
+
"items",
|
|
594
|
+
"not",
|
|
595
|
+
"propertyNames",
|
|
596
|
+
"then",
|
|
597
|
+
"unevaluatedItems",
|
|
598
|
+
"unevaluatedProperties"
|
|
599
|
+
]);
|
|
600
|
+
/** Containers whose value is a list of nested schemas. */
|
|
601
|
+
const SCHEMA_ARRAY_KEYS$1 = /* @__PURE__ */ new Set([
|
|
602
|
+
"allOf",
|
|
603
|
+
"anyOf",
|
|
604
|
+
"items",
|
|
605
|
+
"oneOf",
|
|
606
|
+
"prefixItems"
|
|
607
|
+
]);
|
|
297
608
|
/** Recursively remove schema keywords unsupported by a target provider/tool surface. */
|
|
298
609
|
function stripUnsupportedSchemaKeywords(schema, unsupportedKeywords) {
|
|
299
610
|
if (!schema || typeof schema !== "object") return schema;
|
|
@@ -302,16 +613,16 @@ function stripUnsupportedSchemaKeywords(schema, unsupportedKeywords) {
|
|
|
302
613
|
const cleaned = {};
|
|
303
614
|
for (const [key, value] of Object.entries(obj)) {
|
|
304
615
|
if (unsupportedKeywords.has(key)) continue;
|
|
305
|
-
if (key
|
|
616
|
+
if (SCHEMA_MAP_KEYS$1.has(key) && value && typeof value === "object" && !Array.isArray(value)) {
|
|
306
617
|
cleaned[key] = Object.fromEntries(Object.entries(value).map(([childKey, childValue]) => [childKey, stripUnsupportedSchemaKeywords(childValue, unsupportedKeywords)]));
|
|
307
618
|
continue;
|
|
308
619
|
}
|
|
309
|
-
if (key
|
|
310
|
-
cleaned[key] =
|
|
620
|
+
if (SCHEMA_ARRAY_KEYS$1.has(key) && Array.isArray(value)) {
|
|
621
|
+
cleaned[key] = value.map((entry) => stripUnsupportedSchemaKeywords(entry, unsupportedKeywords));
|
|
311
622
|
continue;
|
|
312
623
|
}
|
|
313
|
-
if ((key
|
|
314
|
-
cleaned[key] =
|
|
624
|
+
if (SCHEMA_OBJECT_KEYS$1.has(key) && value && typeof value === "object") {
|
|
625
|
+
cleaned[key] = stripUnsupportedSchemaKeywords(value, unsupportedKeywords);
|
|
315
626
|
continue;
|
|
316
627
|
}
|
|
317
628
|
cleaned[key] = value;
|
|
@@ -823,9 +1134,11 @@ function normalizeToolParameterSchemaUncached(schema, options) {
|
|
|
823
1134
|
const isAnthropicProvider = normalizedProvider.includes("anthropic");
|
|
824
1135
|
const unsupportedToolSchemaKeywords = resolveUnsupportedToolSchemaKeywords(options?.modelCompat);
|
|
825
1136
|
const omitEmptyArrayItems = shouldOmitEmptyArrayItems(options?.modelCompat);
|
|
1137
|
+
const isLlamacppGbnfProfile = normalizedToolSchemaProfile === "llamacpp";
|
|
826
1138
|
function applyProviderCleaning(s) {
|
|
827
1139
|
const normalizedSchema = normalizeArraySchemasMissingItems(s);
|
|
828
|
-
|
|
1140
|
+
let arrayItemsCompatibleSchema = omitEmptyArrayItems ? stripEmptyArrayItemsFromArraySchemas(normalizedSchema) : normalizedSchema;
|
|
1141
|
+
if (isLlamacppGbnfProfile) arrayItemsCompatibleSchema = cleanSchemaForLlamacppGbnf(arrayItemsCompatibleSchema);
|
|
829
1142
|
if (isGeminiProvider && !isAnthropicProvider) {
|
|
830
1143
|
const geminiCompatibleSchema = cleanSchemaForGemini(arrayItemsCompatibleSchema);
|
|
831
1144
|
return unsupportedToolSchemaKeywords.size > 0 ? stripUnsupportedSchemaKeywords(geminiCompatibleSchema, unsupportedToolSchemaKeywords) : geminiCompatibleSchema;
|
|
@@ -896,322 +1209,6 @@ function normalizeToolParameterSchema(schema, options) {
|
|
|
896
1209
|
return rememberCachedToolParameterSchema(schema, cacheKey, normalizeToolParameterSchemaUncached(schema, options));
|
|
897
1210
|
}
|
|
898
1211
|
//#endregion
|
|
899
|
-
//#region packages/ai/src/providers/openai-reasoning-effort.ts
|
|
900
|
-
/**
|
|
901
|
-
* OpenAI-compatible reasoning-effort normalization. Different GPT families
|
|
902
|
-
* expose different accepted effort enums, so callers map requested values here
|
|
903
|
-
* before constructing provider payloads.
|
|
904
|
-
*/
|
|
905
|
-
const GPT_5_REASONING_EFFORTS = [
|
|
906
|
-
"minimal",
|
|
907
|
-
"low",
|
|
908
|
-
"medium",
|
|
909
|
-
"high"
|
|
910
|
-
];
|
|
911
|
-
const GPT_51_REASONING_EFFORTS = [
|
|
912
|
-
"none",
|
|
913
|
-
"low",
|
|
914
|
-
"medium",
|
|
915
|
-
"high"
|
|
916
|
-
];
|
|
917
|
-
const GPT_52_REASONING_EFFORTS = [
|
|
918
|
-
"none",
|
|
919
|
-
"low",
|
|
920
|
-
"medium",
|
|
921
|
-
"high",
|
|
922
|
-
"xhigh"
|
|
923
|
-
];
|
|
924
|
-
const GPT_56_REASONING_EFFORTS = [
|
|
925
|
-
"none",
|
|
926
|
-
"low",
|
|
927
|
-
"medium",
|
|
928
|
-
"high",
|
|
929
|
-
"xhigh",
|
|
930
|
-
"max"
|
|
931
|
-
];
|
|
932
|
-
const GPT_CODEX_REASONING_EFFORTS = [
|
|
933
|
-
"low",
|
|
934
|
-
"medium",
|
|
935
|
-
"high",
|
|
936
|
-
"xhigh"
|
|
937
|
-
];
|
|
938
|
-
const GPT_PRO_REASONING_EFFORTS = [
|
|
939
|
-
"medium",
|
|
940
|
-
"high",
|
|
941
|
-
"xhigh"
|
|
942
|
-
];
|
|
943
|
-
const GPT_5_PRO_REASONING_EFFORTS = ["high"];
|
|
944
|
-
const GPT_51_CODEX_MAX_REASONING_EFFORTS = [
|
|
945
|
-
"none",
|
|
946
|
-
"medium",
|
|
947
|
-
"high",
|
|
948
|
-
"xhigh"
|
|
949
|
-
];
|
|
950
|
-
const GPT_51_CODEX_MINI_REASONING_EFFORTS = ["medium"];
|
|
951
|
-
const GENERIC_REASONING_EFFORTS = [
|
|
952
|
-
"low",
|
|
953
|
-
"medium",
|
|
954
|
-
"high"
|
|
955
|
-
];
|
|
956
|
-
const CANONICAL_REASONING_EFFORTS = /* @__PURE__ */ new Set([
|
|
957
|
-
"none",
|
|
958
|
-
"minimal",
|
|
959
|
-
"low",
|
|
960
|
-
"medium",
|
|
961
|
-
"high",
|
|
962
|
-
"xhigh",
|
|
963
|
-
"max",
|
|
964
|
-
"off"
|
|
965
|
-
]);
|
|
966
|
-
function normalizeModelId(id) {
|
|
967
|
-
return normalizeLowercaseStringOrEmpty(id ?? "").replace(/-\d{4}-\d{2}-\d{2}$/u, "");
|
|
968
|
-
}
|
|
969
|
-
/** Return whether a model is the GPT-5.4 mini family. */
|
|
970
|
-
function isOpenAIGpt54MiniModel(model) {
|
|
971
|
-
const id = normalizeModelId(typeof model.id === "string" ? model.id : void 0);
|
|
972
|
-
return /^gpt-5\.4-mini(?:-|$)/u.test(id);
|
|
973
|
-
}
|
|
974
|
-
/** Return whether a model is the GPT-5.5 family. */
|
|
975
|
-
function isOpenAIGpt55Model(model) {
|
|
976
|
-
const id = normalizeModelId(typeof model.id === "string" ? model.id : void 0);
|
|
977
|
-
const name = normalizeModelId(typeof model.name === "string" ? model.name : void 0);
|
|
978
|
-
return /^gpt-5\.5(?:-|$)/u.test(id) || /^gpt-5\.5(?:\s|\(|-|$)/u.test(name);
|
|
979
|
-
}
|
|
980
|
-
/** Return whether a model is the GPT-5.6 family. */
|
|
981
|
-
function isOpenAIGpt56Model(model) {
|
|
982
|
-
const id = normalizeModelId(typeof model.id === "string" ? model.id : void 0);
|
|
983
|
-
const name = normalizeModelId(typeof model.name === "string" ? model.name : void 0);
|
|
984
|
-
return /^gpt-5\.6(?:-|$)/u.test(id) || /^gpt-5\.6(?:\s|\(|-|$)/u.test(name);
|
|
985
|
-
}
|
|
986
|
-
/** Normalize user-facing reasoning effort names to API effort names. */
|
|
987
|
-
function normalizeOpenAIReasoningEffort(effort) {
|
|
988
|
-
const trimmed = effort.trim();
|
|
989
|
-
const folded = trimmed.toLowerCase();
|
|
990
|
-
return CANONICAL_REASONING_EFFORTS.has(folded) ? folded : trimmed;
|
|
991
|
-
}
|
|
992
|
-
function readCompatReasoningEfforts(compat) {
|
|
993
|
-
if (!compat || typeof compat !== "object") return;
|
|
994
|
-
if (compat.supportsReasoningEffort === false) return [];
|
|
995
|
-
const raw = compat.supportedReasoningEfforts;
|
|
996
|
-
if (!Array.isArray(raw)) return;
|
|
997
|
-
const supported = uniqueStrings(normalizeStringEntries(raw.filter((value) => typeof value === "string")));
|
|
998
|
-
return supported.length > 0 ? supported : void 0;
|
|
999
|
-
}
|
|
1000
|
-
function isDisabledReasoningEffort(effort) {
|
|
1001
|
-
return effort === "none" || effort === "off";
|
|
1002
|
-
}
|
|
1003
|
-
/** Resolve the reasoning efforts accepted by a specific OpenAI-compatible model. */
|
|
1004
|
-
function resolveOpenAISupportedReasoningEfforts(model) {
|
|
1005
|
-
const compatEfforts = readCompatReasoningEfforts(model.compat);
|
|
1006
|
-
if (compatEfforts) return compatEfforts;
|
|
1007
|
-
const id = normalizeModelId(typeof model.id === "string" ? model.id : void 0);
|
|
1008
|
-
if (/^gpt-5\.6(?:-|$)/u.test(id)) return GPT_56_REASONING_EFFORTS;
|
|
1009
|
-
if (id === "gpt-5.1-codex-mini") return GPT_51_CODEX_MINI_REASONING_EFFORTS;
|
|
1010
|
-
if (id === "gpt-5.1-codex-max") return GPT_51_CODEX_MAX_REASONING_EFFORTS;
|
|
1011
|
-
if (/^gpt-5(?:\.\d+)?-codex(?:-|$)/u.test(id)) return GPT_CODEX_REASONING_EFFORTS;
|
|
1012
|
-
if (id === "gpt-5-pro") return GPT_5_PRO_REASONING_EFFORTS;
|
|
1013
|
-
if (/^gpt-5\.[2-9](?:\.\d+)?-pro(?:-|$)/u.test(id)) return GPT_PRO_REASONING_EFFORTS;
|
|
1014
|
-
if (/^gpt-5\.[2-9](?:\.\d+)?(?:-|$)/u.test(id)) return GPT_52_REASONING_EFFORTS;
|
|
1015
|
-
if (/^gpt-5\.1(?:-|$)/u.test(id)) return GPT_51_REASONING_EFFORTS;
|
|
1016
|
-
if (/^gpt-5(?:-|$)/u.test(id)) return GPT_5_REASONING_EFFORTS;
|
|
1017
|
-
return GENERIC_REASONING_EFFORTS;
|
|
1018
|
-
}
|
|
1019
|
-
/**
|
|
1020
|
-
* Return whether a model accepts the temperature parameter. The GPT-5.6
|
|
1021
|
-
* family rejects it with a 400; catalog compat can override per model.
|
|
1022
|
-
*/
|
|
1023
|
-
function supportsOpenAITemperature(model) {
|
|
1024
|
-
const compat = model.compat;
|
|
1025
|
-
if (compat && typeof compat === "object") {
|
|
1026
|
-
const declared = compat.supportsTemperature;
|
|
1027
|
-
if (typeof declared === "boolean") return declared;
|
|
1028
|
-
}
|
|
1029
|
-
const id = normalizeModelId(typeof model.id === "string" ? model.id : void 0);
|
|
1030
|
-
return !/^gpt-5\.6(?:-|$)/u.test(id);
|
|
1031
|
-
}
|
|
1032
|
-
/** Return whether a model accepts a requested reasoning effort. */
|
|
1033
|
-
function supportsOpenAIReasoningEffort(model, effort) {
|
|
1034
|
-
return resolveOpenAISupportedReasoningEfforts(model).includes(normalizeOpenAIReasoningEffort(effort));
|
|
1035
|
-
}
|
|
1036
|
-
/** Resolve a requested reasoning effort to the closest value supported by the model. */
|
|
1037
|
-
function resolveOpenAIReasoningEffortForModel(params) {
|
|
1038
|
-
const requested = normalizeOpenAIReasoningEffort(params.effort);
|
|
1039
|
-
const mapped = params.fallbackMap?.[requested] ?? (params.fallbackMap && CANONICAL_REASONING_EFFORTS.has(requested) ? Object.entries(params.fallbackMap).find(([effort]) => normalizeOpenAIReasoningEffort(effort) === requested)?.[1] : void 0);
|
|
1040
|
-
const normalized = mapped === void 0 ? requested : mapped.trim();
|
|
1041
|
-
const supported = resolveOpenAISupportedReasoningEfforts(params.model);
|
|
1042
|
-
if (supported.includes(normalized)) return normalized;
|
|
1043
|
-
if (requested === "off" && supported.includes("none")) return "none";
|
|
1044
|
-
if (isDisabledReasoningEffort(requested) || isDisabledReasoningEffort(normalized)) return;
|
|
1045
|
-
if (requested === "minimal" && supported.includes("low")) return "low";
|
|
1046
|
-
if ((requested === "minimal" || requested === "low") && supported.includes("medium")) return "medium";
|
|
1047
|
-
if (requested === "xhigh" && supported.includes("high")) return "high";
|
|
1048
|
-
if (requested === "max" && supported.includes("xhigh")) return "xhigh";
|
|
1049
|
-
return supported.find((effort) => !isDisabledReasoningEffort(normalizeOpenAIReasoningEffort(effort)));
|
|
1050
|
-
}
|
|
1051
|
-
//#endregion
|
|
1052
|
-
//#region packages/ai/src/providers/openai-responses-stream-compat.ts
|
|
1053
|
-
const OPENAI_RESPONSES_OUTPUT_TEXT_CONTENT_PART_TYPE = "output_text";
|
|
1054
|
-
const AZURE_RESPONSES_TEXT_CONTENT_PART_TYPE = "text";
|
|
1055
|
-
const OPENAI_RESPONSES_OUTPUT_TEXT_DELTA_EVENT_TYPE = "response.output_text.delta";
|
|
1056
|
-
const AZURE_RESPONSES_TEXT_DELTA_EVENT_TYPE = "response.text.delta";
|
|
1057
|
-
function isResponsesTextContentPartType(type) {
|
|
1058
|
-
return type === "output_text" || type === "text";
|
|
1059
|
-
}
|
|
1060
|
-
function isResponsesTextDeltaEventType(type) {
|
|
1061
|
-
return type === "response.output_text.delta" || type === "response.text.delta";
|
|
1062
|
-
}
|
|
1063
|
-
function isAzureResponsesTextDeltaEventType(type) {
|
|
1064
|
-
return type === AZURE_RESPONSES_TEXT_DELTA_EVENT_TYPE;
|
|
1065
|
-
}
|
|
1066
|
-
function isAzureResponsesTextDeltaEvent(event) {
|
|
1067
|
-
return isAzureResponsesTextDeltaEventType(event.type) && typeof event.delta === "string";
|
|
1068
|
-
}
|
|
1069
|
-
function resolveResponsesMessageSnapshotCollapse(params) {
|
|
1070
|
-
const { prior, nextText } = params;
|
|
1071
|
-
if (!prior?.text || !nextText || prior.phase !== params.nextPhase) return { kind: "keep" };
|
|
1072
|
-
if (nextText.length > prior.text.length && nextText.startsWith(prior.text)) return {
|
|
1073
|
-
kind: "extend",
|
|
1074
|
-
text: nextText
|
|
1075
|
-
};
|
|
1076
|
-
return { kind: "keep" };
|
|
1077
|
-
}
|
|
1078
|
-
//#endregion
|
|
1079
|
-
//#region packages/ai/src/providers/openai-responses-terminal-usage.ts
|
|
1080
|
-
function readCount(value) {
|
|
1081
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1082
|
-
}
|
|
1083
|
-
/**
|
|
1084
|
-
* Split a terminal usage payload into the priced buckets.
|
|
1085
|
-
*
|
|
1086
|
-
* OpenAI includes cache reads and writes in `input_tokens`, so both are subtracted out of the
|
|
1087
|
-
* billable input bucket. `total_tokens` comes from the payload, but never below the sum of the
|
|
1088
|
-
* split buckets: proxies routinely omit it (reporting 0 would understate the turn), and a payload
|
|
1089
|
-
* whose `cached_tokens` exceeds `input_tokens` clamps the input bucket, leaving the reported total
|
|
1090
|
-
* short of what the buckets actually price.
|
|
1091
|
-
*/
|
|
1092
|
-
function mapResponsesTerminalUsage(usage) {
|
|
1093
|
-
if (!usage) return;
|
|
1094
|
-
const cacheRead = readCount(usage.input_tokens_details?.cached_tokens);
|
|
1095
|
-
const cacheWrite = readCount(usage.input_tokens_details?.cache_write_tokens);
|
|
1096
|
-
const input = Math.max(0, readCount(usage.input_tokens) - cacheRead - cacheWrite);
|
|
1097
|
-
const output = readCount(usage.output_tokens);
|
|
1098
|
-
const bucketTotal = input + output + cacheRead + cacheWrite;
|
|
1099
|
-
return {
|
|
1100
|
-
input,
|
|
1101
|
-
output,
|
|
1102
|
-
cacheRead,
|
|
1103
|
-
cacheWrite,
|
|
1104
|
-
totalTokens: Math.max(bucketTotal, readCount(usage.total_tokens))
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
/** Reasoning tokens are reported by the agent path only; the package path does not track them. */
|
|
1108
|
-
function readResponsesReasoningTokens(usage) {
|
|
1109
|
-
const reasoningTokens = usage?.output_tokens_details?.reasoning_tokens;
|
|
1110
|
-
return typeof reasoningTokens === "number" && Number.isFinite(reasoningTokens) ? reasoningTokens : void 0;
|
|
1111
|
-
}
|
|
1112
|
-
function mapResponsesTerminalStopReason(status) {
|
|
1113
|
-
if (!status) return "stop";
|
|
1114
|
-
switch (status) {
|
|
1115
|
-
case "completed": return "stop";
|
|
1116
|
-
case "incomplete": return "length";
|
|
1117
|
-
case "failed":
|
|
1118
|
-
case "cancelled": return "error";
|
|
1119
|
-
case "in_progress":
|
|
1120
|
-
case "queued": return "stop";
|
|
1121
|
-
default: throw new Error(`Unhandled stop reason: ${String(status)}`);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* Resolve the terminal stop reason, including the two overrides every Responses path shares: a
|
|
1126
|
-
* content-filtered turn is a provider error rather than a truncated answer, and a turn that
|
|
1127
|
-
* produced tool calls reports `toolUse` instead of a plain stop.
|
|
1128
|
-
*/
|
|
1129
|
-
function resolveResponsesTerminalStopReason(params) {
|
|
1130
|
-
if (params.status === "incomplete" && params.incompleteReason === "content_filter") return {
|
|
1131
|
-
stopReason: "error",
|
|
1132
|
-
errorMessage: "Provider incomplete_reason: content_filter"
|
|
1133
|
-
};
|
|
1134
|
-
const stopReason = mapResponsesTerminalStopReason(params.status);
|
|
1135
|
-
if (stopReason === "stop" && params.hasToolCall) return { stopReason: "toolUse" };
|
|
1136
|
-
return { stopReason };
|
|
1137
|
-
}
|
|
1138
|
-
//#endregion
|
|
1139
|
-
//#region packages/ai/src/providers/openai-responses-tool-call-tracker.ts
|
|
1140
|
-
function readIdentityValue(value) {
|
|
1141
|
-
return (typeof value === "string" ? value.trim() : "") || void 0;
|
|
1142
|
-
}
|
|
1143
|
-
function readOutputIndex(event) {
|
|
1144
|
-
return typeof event.output_index === "number" && Number.isInteger(event.output_index) && event.output_index >= 0 ? event.output_index : void 0;
|
|
1145
|
-
}
|
|
1146
|
-
function readEventIdentity(event) {
|
|
1147
|
-
return { itemId: readIdentityValue(event.item_id) };
|
|
1148
|
-
}
|
|
1149
|
-
function readResponsesToolCallItemIdentity(item) {
|
|
1150
|
-
return {
|
|
1151
|
-
itemId: readIdentityValue(item.id),
|
|
1152
|
-
callId: readIdentityValue(item.call_id)
|
|
1153
|
-
};
|
|
1154
|
-
}
|
|
1155
|
-
function createResponsesToolCallTracker() {
|
|
1156
|
-
const indexedCalls = /* @__PURE__ */ new Map();
|
|
1157
|
-
const unindexedCalls = /* @__PURE__ */ new Set();
|
|
1158
|
-
const identitiesConflict = (state, identity) => Boolean(state.itemId && identity.itemId && state.itemId !== identity.itemId || state.callId && identity.callId && state.callId !== identity.callId);
|
|
1159
|
-
const sharesIdentity = (state, identity) => Boolean(state.itemId && identity.itemId && state.itemId === identity.itemId || state.callId && identity.callId && state.callId === identity.callId);
|
|
1160
|
-
const adoptIdentity = (state, identity) => {
|
|
1161
|
-
state.itemId ??= identity.itemId;
|
|
1162
|
-
state.callId ??= identity.callId;
|
|
1163
|
-
return state;
|
|
1164
|
-
};
|
|
1165
|
-
const resolveCompatible = (candidates, identity) => {
|
|
1166
|
-
const uniqueCandidates = [...new Set(candidates)];
|
|
1167
|
-
if (!identity.itemId && !identity.callId) return uniqueCandidates.length === 1 ? uniqueCandidates.at(0) : void 0;
|
|
1168
|
-
const compatible = uniqueCandidates.filter((state) => !identitiesConflict(state, identity));
|
|
1169
|
-
const matches = compatible.filter((state) => sharesIdentity(state, identity));
|
|
1170
|
-
const matched = matches.length === 1 ? matches.at(0) : void 0;
|
|
1171
|
-
if (matched) return adoptIdentity(matched, identity);
|
|
1172
|
-
const soleCompatible = uniqueCandidates.length === 1 && compatible.length === 1 && matches.length === 0 ? compatible.at(0) : void 0;
|
|
1173
|
-
return soleCompatible ? adoptIdentity(soleCompatible, identity) : void 0;
|
|
1174
|
-
};
|
|
1175
|
-
return {
|
|
1176
|
-
register(event, state) {
|
|
1177
|
-
const outputIndex = readOutputIndex(event);
|
|
1178
|
-
if (outputIndex === void 0) {
|
|
1179
|
-
unindexedCalls.add(state);
|
|
1180
|
-
return;
|
|
1181
|
-
}
|
|
1182
|
-
if (indexedCalls.has(outputIndex)) throw new Error(`Responses stream reused active tool-call output index ${outputIndex}`);
|
|
1183
|
-
indexedCalls.set(outputIndex, state);
|
|
1184
|
-
},
|
|
1185
|
-
resolve(event, identity = readEventIdentity(event)) {
|
|
1186
|
-
const outputIndex = readOutputIndex(event);
|
|
1187
|
-
if (outputIndex !== void 0) {
|
|
1188
|
-
const indexed = indexedCalls.get(outputIndex);
|
|
1189
|
-
if (indexed) {
|
|
1190
|
-
if (indexed.callId && identity.callId && indexed.callId !== identity.callId) return;
|
|
1191
|
-
return adoptIdentity(indexed, identity);
|
|
1192
|
-
}
|
|
1193
|
-
const unindexed = resolveCompatible(unindexedCalls, identity);
|
|
1194
|
-
if (unindexed) {
|
|
1195
|
-
unindexedCalls.delete(unindexed);
|
|
1196
|
-
indexedCalls.set(outputIndex, unindexed);
|
|
1197
|
-
}
|
|
1198
|
-
return unindexed;
|
|
1199
|
-
}
|
|
1200
|
-
return resolveCompatible([...indexedCalls.values(), ...unindexedCalls], identity);
|
|
1201
|
-
},
|
|
1202
|
-
forget(toolCall) {
|
|
1203
|
-
for (const [outputIndex, tracked] of indexedCalls) if (tracked === toolCall) indexedCalls.delete(outputIndex);
|
|
1204
|
-
unindexedCalls.delete(toolCall);
|
|
1205
|
-
},
|
|
1206
|
-
markArgumentsUnreliable() {
|
|
1207
|
-
for (const toolCall of /* @__PURE__ */ new Set([...indexedCalls.values(), ...unindexedCalls])) toolCall.argumentStreamReliable = false;
|
|
1208
|
-
},
|
|
1209
|
-
hasActive() {
|
|
1210
|
-
return indexedCalls.size > 0 || unindexedCalls.size > 0;
|
|
1211
|
-
}
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
//#endregion
|
|
1215
1212
|
//#region packages/ai/src/providers/openai-tool-schema-compat.ts
|
|
1216
1213
|
const OPENAI_STRICT_COMPAT_SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set([
|
|
1217
1214
|
"$defs",
|
|
@@ -1431,168 +1428,525 @@ function normalizeStrictOpenAIJsonSchemaRecursive(schema, depth) {
|
|
|
1431
1428
|
changed = true;
|
|
1432
1429
|
}
|
|
1433
1430
|
}
|
|
1434
|
-
return changed ? normalized : schema;
|
|
1435
|
-
}
|
|
1436
|
-
/** Normalizes tool parameters using strict OpenAI rules only when strict mode is active. */
|
|
1437
|
-
function normalizeOpenAIStrictToolParameters(schema, strict, modelCompat) {
|
|
1438
|
-
const toolSchemaCompat = resolveToolSchemaModelCompat(modelCompat);
|
|
1439
|
-
if (!strict) return normalizeToolParameterSchema(schema ?? {}, { modelCompat: toolSchemaCompat });
|
|
1440
|
-
return normalizeStrictOpenAIJsonSchema(schema, toolSchemaCompat);
|
|
1431
|
+
return changed ? normalized : schema;
|
|
1432
|
+
}
|
|
1433
|
+
/** Normalizes tool parameters using strict OpenAI rules only when strict mode is active. */
|
|
1434
|
+
function normalizeOpenAIStrictToolParameters(schema, strict, modelCompat) {
|
|
1435
|
+
const toolSchemaCompat = resolveToolSchemaModelCompat(modelCompat);
|
|
1436
|
+
if (!strict) return normalizeToolParameterSchema(schema ?? {}, { modelCompat: toolSchemaCompat });
|
|
1437
|
+
return normalizeStrictOpenAIJsonSchema(schema, toolSchemaCompat);
|
|
1438
|
+
}
|
|
1439
|
+
/** Returns whether a schema already satisfies OpenAI strict tool-schema constraints. */
|
|
1440
|
+
function isStrictOpenAIJsonSchemaCompatible(schema) {
|
|
1441
|
+
return isStrictOpenAIJsonSchemaCompatibleRecursive(normalizeStrictOpenAIJsonSchema(schema));
|
|
1442
|
+
}
|
|
1443
|
+
/** Returns strict-schema diagnostics for an already materialized OpenAI tool projection. */
|
|
1444
|
+
function findOpenAIStrictToolProjectionDiagnostics(projection) {
|
|
1445
|
+
return [...projection.diagnostics.map((diagnostic) => ({
|
|
1446
|
+
toolIndex: diagnostic.toolIndex,
|
|
1447
|
+
...diagnostic.toolName ? { toolName: diagnostic.toolName } : {},
|
|
1448
|
+
violations: [...diagnostic.violations]
|
|
1449
|
+
})), ...projection.tools.flatMap((tool) => {
|
|
1450
|
+
const violations = findOpenAIStrictSchemaViolations(normalizeStrictOpenAIJsonSchema(tool.parameters), `${tool.name}.parameters`);
|
|
1451
|
+
return violations.length > 0 ? [{
|
|
1452
|
+
toolIndex: tool.toolIndex,
|
|
1453
|
+
toolName: tool.name,
|
|
1454
|
+
violations
|
|
1455
|
+
}] : [];
|
|
1456
|
+
})];
|
|
1457
|
+
}
|
|
1458
|
+
function isStrictOpenAIJsonSchemaCompatibleRecursive(schema) {
|
|
1459
|
+
if (Array.isArray(schema)) return schema.every((entry) => isStrictOpenAIJsonSchemaCompatibleRecursive(entry));
|
|
1460
|
+
if (!schema || typeof schema !== "object") return true;
|
|
1461
|
+
const record = schema;
|
|
1462
|
+
if ("anyOf" in record || "oneOf" in record || "allOf" in record) return false;
|
|
1463
|
+
if (Array.isArray(record.type)) return false;
|
|
1464
|
+
if (record.type === "object" && record.additionalProperties !== false) return false;
|
|
1465
|
+
if (record.type === "object") {
|
|
1466
|
+
const properties = record.properties && typeof record.properties === "object" && !Array.isArray(record.properties) ? record.properties : {};
|
|
1467
|
+
const required = Array.isArray(record.required) ? record.required.filter((entry) => typeof entry === "string") : void 0;
|
|
1468
|
+
if (!required) return false;
|
|
1469
|
+
const requiredSet = new Set(required);
|
|
1470
|
+
if (Object.keys(properties).some((key) => !requiredSet.has(key))) return false;
|
|
1471
|
+
}
|
|
1472
|
+
return Object.entries(record).every(([key, entry]) => {
|
|
1473
|
+
if (key === "properties" && entry && typeof entry === "object" && !Array.isArray(entry)) return Object.values(entry).every((value) => isStrictOpenAIJsonSchemaCompatibleRecursive(value));
|
|
1474
|
+
return isStrictOpenAIJsonSchemaCompatibleRecursive(entry);
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
/** Resolves strict mode for the projected tools that will be emitted in the request payload. */
|
|
1478
|
+
function resolveOpenAIProjectedToolsStrictToolFlag(projection, strict) {
|
|
1479
|
+
if (strict !== true) return strict === false ? false : void 0;
|
|
1480
|
+
return projection.tools.every((tool) => isStrictOpenAIJsonSchemaCompatible(tool.parameters));
|
|
1481
|
+
}
|
|
1482
|
+
//#endregion
|
|
1483
|
+
//#region packages/ai/src/transports/openai-transport-shared.ts
|
|
1484
|
+
/** Shared options, usage shape, cache identity, ordering, and stream scheduling for OpenAI APIs. */
|
|
1485
|
+
const MODEL_STREAM_COOPERATIVE_YIELD_INTERVAL_MS = 12;
|
|
1486
|
+
const MODEL_STREAM_COOPERATIVE_YIELD_MAX_EVENTS = 64;
|
|
1487
|
+
const GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP = "skip_thought_signature_validator";
|
|
1488
|
+
const log = {
|
|
1489
|
+
debug(message, data) {
|
|
1490
|
+
getAiTransportHost().logDebug("openai-transport", () => ({
|
|
1491
|
+
message,
|
|
1492
|
+
data
|
|
1493
|
+
}));
|
|
1494
|
+
},
|
|
1495
|
+
info(message, data) {
|
|
1496
|
+
getAiTransportHost().logInfo("openai-transport", message, data);
|
|
1497
|
+
},
|
|
1498
|
+
warn(message, data) {
|
|
1499
|
+
getAiTransportHost().logWarn("openai-transport", message, data);
|
|
1500
|
+
}
|
|
1501
|
+
};
|
|
1502
|
+
function throwIfModelStreamAborted(signal) {
|
|
1503
|
+
if (signal?.aborted) throw transportAbortError(signal);
|
|
1504
|
+
}
|
|
1505
|
+
function createModelStreamCooperativeScheduler(signal) {
|
|
1506
|
+
let lastYieldedAt = Date.now();
|
|
1507
|
+
let eventsSinceYield = 0;
|
|
1508
|
+
return { async afterEvent() {
|
|
1509
|
+
throwIfModelStreamAborted(signal);
|
|
1510
|
+
eventsSinceYield += 1;
|
|
1511
|
+
const now = Date.now();
|
|
1512
|
+
if (eventsSinceYield < MODEL_STREAM_COOPERATIVE_YIELD_MAX_EVENTS && now - lastYieldedAt < MODEL_STREAM_COOPERATIVE_YIELD_INTERVAL_MS) return;
|
|
1513
|
+
eventsSinceYield = 0;
|
|
1514
|
+
lastYieldedAt = now;
|
|
1515
|
+
await new Promise((resolve) => {
|
|
1516
|
+
setTimeout(resolve, 0);
|
|
1517
|
+
});
|
|
1518
|
+
throwIfModelStreamAborted(signal);
|
|
1519
|
+
} };
|
|
1520
|
+
}
|
|
1521
|
+
function resolvePromptCacheKey(options, cacheRetention) {
|
|
1522
|
+
if (cacheRetention === "none") return;
|
|
1523
|
+
return clampOpenAIPromptCacheKey(options?.promptCacheKey ?? options?.sessionId);
|
|
1524
|
+
}
|
|
1525
|
+
//#endregion
|
|
1526
|
+
//#region packages/ai/src/transports/openai-responses-replay.ts
|
|
1527
|
+
/** Resolves the assistant message id that can be replayed to OpenAI Responses. */
|
|
1528
|
+
function resolveReplayableResponsesMessageId(params) {
|
|
1529
|
+
if (!params.replayResponsesItemIds) return;
|
|
1530
|
+
if (!params.textSignatureId) return params.fallbackOrdinal === 0 ? params.fallbackId : `${params.fallbackId}_${params.fallbackOrdinal}`;
|
|
1531
|
+
return params.previousReplayItemWasReasoning ? params.textSignatureId : void 0;
|
|
1532
|
+
}
|
|
1533
|
+
const OPENAI_CODEX_RESPONSES_DEFAULT_INSTRUCTIONS = "Follow the user request.";
|
|
1534
|
+
const AZURE_RESPONSES_FIRST_EVENT_TIMEOUT_MS = 3e4;
|
|
1535
|
+
const RESPONSE_FAILED_NO_DETAILS_MESSAGE = "Unknown error (no error details in response)";
|
|
1536
|
+
const OPENAI_RESPONSES_REASONING_REPLAY_META_KEY = "__openclaw_replay";
|
|
1537
|
+
const OPENAI_RESPONSES_REASONING_REPLAY_BLOCK_META_KEY = "openclawReasoningReplay";
|
|
1538
|
+
//#endregion
|
|
1539
|
+
//#region packages/ai/src/transports/openai-responses-debug.ts
|
|
1540
|
+
function stringifyUnknown(value, fallback = "") {
|
|
1541
|
+
if (typeof value === "string") return value;
|
|
1542
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1543
|
+
return fallback;
|
|
1544
|
+
}
|
|
1545
|
+
function getServiceTierCostMultiplier(serviceTier) {
|
|
1546
|
+
switch (serviceTier) {
|
|
1547
|
+
case "flex": return .5;
|
|
1548
|
+
case "priority": return 2;
|
|
1549
|
+
default: return 1;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
function applyServiceTierPricing(usage, serviceTier) {
|
|
1553
|
+
const multiplier = getServiceTierCostMultiplier(serviceTier);
|
|
1554
|
+
if (multiplier === 1) return;
|
|
1555
|
+
usage.cost.input *= multiplier;
|
|
1556
|
+
usage.cost.output *= multiplier;
|
|
1557
|
+
usage.cost.cacheRead *= multiplier;
|
|
1558
|
+
usage.cost.cacheWrite *= multiplier;
|
|
1559
|
+
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
|
|
1560
|
+
}
|
|
1561
|
+
function safeDebugValue(value) {
|
|
1562
|
+
if (typeof value === "string") return value;
|
|
1563
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1564
|
+
if (value === null) return "null";
|
|
1565
|
+
if (value === void 0) return "undefined";
|
|
1566
|
+
return Array.isArray(value) ? "array" : typeof value;
|
|
1567
|
+
}
|
|
1568
|
+
function responseInputTextChars(input) {
|
|
1569
|
+
if (typeof input === "string") return input.length;
|
|
1570
|
+
if (Array.isArray(input)) return input.reduce((total, item) => total + responseInputTextChars(item), 0);
|
|
1571
|
+
if (!input || typeof input !== "object") return 0;
|
|
1572
|
+
const record = input;
|
|
1573
|
+
let total = 0;
|
|
1574
|
+
if (typeof record.text === "string") total += record.text.length;
|
|
1575
|
+
if (typeof record.content === "string") total += record.content.length;
|
|
1576
|
+
else if (Array.isArray(record.content)) total += responseInputTextChars(record.content);
|
|
1577
|
+
return total;
|
|
1578
|
+
}
|
|
1579
|
+
function responseInputRoles(input) {
|
|
1580
|
+
if (!Array.isArray(input)) return "";
|
|
1581
|
+
const roles = /* @__PURE__ */ new Set();
|
|
1582
|
+
for (const item of input) if (item && typeof item === "object") {
|
|
1583
|
+
const role = item.role;
|
|
1584
|
+
if (typeof role === "string" && role.trim()) roles.add(role.trim());
|
|
1585
|
+
}
|
|
1586
|
+
return [...roles].toSorted().join(",");
|
|
1587
|
+
}
|
|
1588
|
+
function readToolPayloadField(record, field) {
|
|
1589
|
+
try {
|
|
1590
|
+
return record[field];
|
|
1591
|
+
} catch {
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
function readResponsesToolDisplayName(tool) {
|
|
1596
|
+
if (!tool || typeof tool !== "object") return "";
|
|
1597
|
+
const record = tool;
|
|
1598
|
+
const name = readToolPayloadField(record, "name");
|
|
1599
|
+
if (typeof name === "string") return name;
|
|
1600
|
+
const fn = readToolPayloadField(record, "function");
|
|
1601
|
+
if (fn && typeof fn === "object") {
|
|
1602
|
+
const fnName = readToolPayloadField(fn, "name");
|
|
1603
|
+
if (typeof fnName === "string") return fnName;
|
|
1604
|
+
}
|
|
1605
|
+
const type = readToolPayloadField(record, "type");
|
|
1606
|
+
return typeof type === "string" && type !== "function" ? type : "";
|
|
1607
|
+
}
|
|
1608
|
+
function summarizeResponsesTools(tools) {
|
|
1609
|
+
if (!Array.isArray(tools)) return "count=0";
|
|
1610
|
+
const names = tools.map(readResponsesToolDisplayName).filter(Boolean);
|
|
1611
|
+
const mode = resolveModelPayloadDebugMode();
|
|
1612
|
+
const maxNames = mode === "tools" || mode === "full-redacted" ? names.length : 12;
|
|
1613
|
+
const label = maxNames >= names.length ? "names" : "sample";
|
|
1614
|
+
const shown = names.slice(0, maxNames).join(",");
|
|
1615
|
+
return `count=${tools.length}${shown ? ` ${label}=${shown}` : ""}`;
|
|
1616
|
+
}
|
|
1617
|
+
function stringifyRedactedPayload(value) {
|
|
1618
|
+
try {
|
|
1619
|
+
const encoded = JSON.stringify(value);
|
|
1620
|
+
if (!encoded) return "<empty>";
|
|
1621
|
+
const redacted = redactSensitiveText(encoded, { mode: "tools" });
|
|
1622
|
+
return redacted.length > 8e3 ? `${truncateUtf16Safe(redacted, 8e3)}…<truncated>` : redacted;
|
|
1623
|
+
} catch {
|
|
1624
|
+
return "<unserializable>";
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
function stringifyRedactedEvent(value) {
|
|
1628
|
+
const redacted = stringifyRedactedPayload(value);
|
|
1629
|
+
return redacted.length > 2e3 ? `${truncateUtf16Safe(redacted, 2e3)}…<truncated>` : redacted;
|
|
1630
|
+
}
|
|
1631
|
+
const RESPONSE_FAILED_FAILURE_FIELD_KEYS = [
|
|
1632
|
+
"error",
|
|
1633
|
+
"incomplete_details",
|
|
1634
|
+
"status_details",
|
|
1635
|
+
"failure_reason",
|
|
1636
|
+
"last_error",
|
|
1637
|
+
"provider_error",
|
|
1638
|
+
"error_details"
|
|
1639
|
+
];
|
|
1640
|
+
function readResponseFailedString(record, key) {
|
|
1641
|
+
return stringifyUnknown(record?.[key]);
|
|
1642
|
+
}
|
|
1643
|
+
function buildResponsesFailedEventSummary(message, responseId, observation) {
|
|
1644
|
+
const summary = { message };
|
|
1645
|
+
if (responseId) summary.responseId = responseId;
|
|
1646
|
+
if (observation) summary.observation = observation;
|
|
1647
|
+
return summary;
|
|
1648
|
+
}
|
|
1649
|
+
function isResponseFailedIdentifierKey(key) {
|
|
1650
|
+
const normalized = key.replace(/[-_\s]/g, "").toLowerCase();
|
|
1651
|
+
return normalized === "requestid" || normalized === "xrequestid" || normalized === "providerrequestid" || normalized === "providerresponseid" || normalized === "litellmrequestid" || normalized.includes("request") && normalized.endsWith("id") || normalized.includes("provider") && normalized.endsWith("id");
|
|
1652
|
+
}
|
|
1653
|
+
function collectResponseFailedIdentifierHashes(value, opts = {}) {
|
|
1654
|
+
const path = opts.path ?? "";
|
|
1655
|
+
const depth = opts.depth ?? 0;
|
|
1656
|
+
const identifierKey = opts.identifierKey ?? "";
|
|
1657
|
+
const out = opts.out ?? [];
|
|
1658
|
+
const seen = opts.seen ?? /* @__PURE__ */ new WeakSet();
|
|
1659
|
+
if (out.length >= 12 || depth > 4 || !value || typeof value !== "object") return out;
|
|
1660
|
+
if (seen.has(value)) return out;
|
|
1661
|
+
seen.add(value);
|
|
1662
|
+
if (Array.isArray(value)) {
|
|
1663
|
+
for (const [index, item] of value.entries()) {
|
|
1664
|
+
if (index >= 8 || out.length >= 12) break;
|
|
1665
|
+
const itemString = typeof item === "string" || typeof item === "number" ? String(item).trim() : "";
|
|
1666
|
+
if (identifierKey && isResponseFailedIdentifierKey(identifierKey) && itemString) {
|
|
1667
|
+
out.push(`${path}[${index}]=${redactIdentifier(itemString, { len: 12 })}`);
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
collectResponseFailedIdentifierHashes(item, {
|
|
1671
|
+
path: `${path}[${index}]`,
|
|
1672
|
+
depth: depth + 1,
|
|
1673
|
+
identifierKey,
|
|
1674
|
+
out,
|
|
1675
|
+
seen
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
return out;
|
|
1679
|
+
}
|
|
1680
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1681
|
+
if (out.length >= 12) break;
|
|
1682
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
1683
|
+
const childString = typeof child === "string" || typeof child === "number" ? String(child).trim() : "";
|
|
1684
|
+
if (isResponseFailedIdentifierKey(key) && childString) {
|
|
1685
|
+
out.push(`${childPath}=${redactIdentifier(childString, { len: 12 })}`);
|
|
1686
|
+
continue;
|
|
1687
|
+
}
|
|
1688
|
+
collectResponseFailedIdentifierHashes(child, {
|
|
1689
|
+
path: childPath,
|
|
1690
|
+
depth: depth + 1,
|
|
1691
|
+
identifierKey: isResponseFailedIdentifierKey(key) ? key : void 0,
|
|
1692
|
+
out,
|
|
1693
|
+
seen
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
return out;
|
|
1697
|
+
}
|
|
1698
|
+
function redactResponseFailedDiagnosticValue(value, opts = {}) {
|
|
1699
|
+
const key = opts.key ?? "";
|
|
1700
|
+
const depth = opts.depth ?? 0;
|
|
1701
|
+
if (typeof value === "string" || typeof value === "number") return key && isResponseFailedIdentifierKey(key) ? redactIdentifier(String(value), { len: 12 }) : value;
|
|
1702
|
+
if (depth > 6 || !value || typeof value !== "object") return value;
|
|
1703
|
+
const seen = opts.seen ?? /* @__PURE__ */ new WeakSet();
|
|
1704
|
+
if (seen.has(value)) return "<circular>";
|
|
1705
|
+
seen.add(value);
|
|
1706
|
+
if (Array.isArray(value)) return value.slice(0, 16).map((item) => redactResponseFailedDiagnosticValue(item, {
|
|
1707
|
+
key,
|
|
1708
|
+
depth: depth + 1,
|
|
1709
|
+
seen
|
|
1710
|
+
}));
|
|
1711
|
+
const out = {};
|
|
1712
|
+
for (const [childKey, child] of Object.entries(value)) out[childKey] = redactResponseFailedDiagnosticValue(child, {
|
|
1713
|
+
key: childKey,
|
|
1714
|
+
depth: depth + 1,
|
|
1715
|
+
seen
|
|
1716
|
+
});
|
|
1717
|
+
return out;
|
|
1718
|
+
}
|
|
1719
|
+
function buildResponsesFailedFailureFields(response) {
|
|
1720
|
+
if (!response) return {};
|
|
1721
|
+
const fields = {};
|
|
1722
|
+
for (const key of RESPONSE_FAILED_FAILURE_FIELD_KEYS) if (response[key] !== void 0 && response[key] !== null) fields[key] = response[key];
|
|
1723
|
+
return fields;
|
|
1724
|
+
}
|
|
1725
|
+
function buildResponsesFailedNoDetailsObservation(event, model, response = isRecord(event.response) ? event.response : void 0) {
|
|
1726
|
+
const failureFields = redactResponseFailedDiagnosticValue(buildResponsesFailedFailureFields(response));
|
|
1727
|
+
const metadataKeys = isRecord(response?.metadata) ? Object.keys(response.metadata).toSorted() : [];
|
|
1728
|
+
const responsePreview = {
|
|
1729
|
+
id: readResponseFailedString(response, "id"),
|
|
1730
|
+
status: readResponseFailedString(response, "status"),
|
|
1731
|
+
model: readResponseFailedString(response, "model"),
|
|
1732
|
+
object: readResponseFailedString(response, "object"),
|
|
1733
|
+
failureFields,
|
|
1734
|
+
metadataKeys
|
|
1735
|
+
};
|
|
1736
|
+
return {
|
|
1737
|
+
event: "openai_responses_response_failed_without_details",
|
|
1738
|
+
provider: model.provider,
|
|
1739
|
+
api: model.api,
|
|
1740
|
+
transportModel: model.id,
|
|
1741
|
+
providerRuntimeFailureKind: "no_error_details",
|
|
1742
|
+
responseId: responsePreview.id,
|
|
1743
|
+
responseStatus: responsePreview.status,
|
|
1744
|
+
responseModel: responsePreview.model,
|
|
1745
|
+
responseObject: responsePreview.object,
|
|
1746
|
+
metadataKeys,
|
|
1747
|
+
requestIdHashes: collectResponseFailedIdentifierHashes(event),
|
|
1748
|
+
failureFieldsPreview: stringifyRedactedEvent(failureFields),
|
|
1749
|
+
responsePreview: stringifyRedactedEvent(responsePreview)
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
function summarizeResponsesFailedNoDetailsObservation(observation) {
|
|
1753
|
+
const requestIds = observation.requestIdHashes.join(",");
|
|
1754
|
+
const metadataKeys = observation.metadataKeys.join(",");
|
|
1755
|
+
return `responseId=${safeDebugValue(observation.responseId || void 0)} responseStatus=${safeDebugValue(observation.responseStatus || void 0)} responseModel=${safeDebugValue(observation.responseModel || void 0)} requestIds=${requestIds || "none"} metadataKeys=${metadataKeys || "none"} failureFields=${observation.failureFieldsPreview}`;
|
|
1756
|
+
}
|
|
1757
|
+
function normalizeResponsesFailedEvent(event, model) {
|
|
1758
|
+
const response = isRecord(event.response) ? event.response : void 0;
|
|
1759
|
+
const responseId = readResponseFailedString(response, "id") || void 0;
|
|
1760
|
+
const error = isRecord(response?.error) ? response.error : void 0;
|
|
1761
|
+
if (error) {
|
|
1762
|
+
const code = readResponseFailedString(error, "code").trim();
|
|
1763
|
+
const message = readResponseFailedString(error, "message").trim();
|
|
1764
|
+
if (code || message) return buildResponsesFailedEventSummary(`${code || "unknown"}: ${message || "no message"}`, responseId);
|
|
1765
|
+
}
|
|
1766
|
+
const incompleteReason = readResponseFailedString(isRecord(response?.incomplete_details) ? response.incomplete_details : void 0, "reason");
|
|
1767
|
+
if (incompleteReason) return buildResponsesFailedEventSummary(`incomplete: ${incompleteReason}`, responseId);
|
|
1768
|
+
return buildResponsesFailedEventSummary(RESPONSE_FAILED_NO_DETAILS_MESSAGE, responseId, buildResponsesFailedNoDetailsObservation(event, model, response));
|
|
1769
|
+
}
|
|
1770
|
+
function logResponsesFailedNoDetails(observation) {
|
|
1771
|
+
log.warn(`[responses] response.failed missing error details provider=${observation.provider} api=${observation.api} model=${observation.transportModel} ` + summarizeResponsesFailedNoDetailsObservation(observation), observation);
|
|
1772
|
+
}
|
|
1773
|
+
function summarizeResponsesPayload(params) {
|
|
1774
|
+
if (!params || typeof params !== "object") return "payload=non-object";
|
|
1775
|
+
const record = params;
|
|
1776
|
+
const input = record.input;
|
|
1777
|
+
const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
|
|
1778
|
+
const text = record.text && typeof record.text === "object" ? record.text : void 0;
|
|
1779
|
+
const parts = [
|
|
1780
|
+
`fields=${Object.keys(record).toSorted().join(",")}`,
|
|
1781
|
+
`model=${safeDebugValue(record.model)}`,
|
|
1782
|
+
`stream=${safeDebugValue(record.stream)}`,
|
|
1783
|
+
`inputItems=${Array.isArray(input) ? input.length : typeof input}`,
|
|
1784
|
+
`inputRoles=${responseInputRoles(input) || "none"}`,
|
|
1785
|
+
`inputTextChars=${responseInputTextChars(input)}`,
|
|
1786
|
+
`tools=${summarizeResponsesTools(record.tools)}`,
|
|
1787
|
+
`reasoningEffort=${safeDebugValue(reasoning?.effort)}`,
|
|
1788
|
+
`reasoningSummary=${safeDebugValue(reasoning?.summary)}`,
|
|
1789
|
+
`textVerbosity=${safeDebugValue(text?.verbosity)}`,
|
|
1790
|
+
`serviceTier=${safeDebugValue(record.service_tier)}`,
|
|
1791
|
+
`store=${safeDebugValue(record.store)}`,
|
|
1792
|
+
`promptCacheKey=${record.prompt_cache_key === void 0 ? "absent" : "present"}`,
|
|
1793
|
+
`metadataKeys=${record.metadata && typeof record.metadata === "object" ? Object.keys(record.metadata).toSorted().join(",") : "none"}`
|
|
1794
|
+
];
|
|
1795
|
+
if (resolveModelPayloadDebugMode() === "full-redacted") parts.push(`payload=${stringifyRedactedPayload(record)}`);
|
|
1796
|
+
return parts.join(" ");
|
|
1797
|
+
}
|
|
1798
|
+
function summarizeOpenAITransportError(error) {
|
|
1799
|
+
if (!error || typeof error !== "object") return `type=${typeof error} message=${safeDebugValue(error)}`;
|
|
1800
|
+
const record = error;
|
|
1801
|
+
const cause = record.cause && typeof record.cause === "object" ? record.cause : void 0;
|
|
1802
|
+
return [
|
|
1803
|
+
`name=${safeDebugValue(record.name)}`,
|
|
1804
|
+
`status=${safeDebugValue(record.status)}`,
|
|
1805
|
+
`code=${safeDebugValue(record.code)}`,
|
|
1806
|
+
`type=${safeDebugValue(record.type)}`,
|
|
1807
|
+
`causeName=${safeDebugValue(cause?.name)}`,
|
|
1808
|
+
`causeCode=${safeDebugValue(cause?.code)}`,
|
|
1809
|
+
`message=${error instanceof Error ? error.message : safeDebugValue(error)}`
|
|
1810
|
+
].join(" ");
|
|
1811
|
+
}
|
|
1812
|
+
//#endregion
|
|
1813
|
+
//#region packages/ai/src/transports/openai-responses-replay-internal.ts
|
|
1814
|
+
function isInvalidEncryptedContentError(error) {
|
|
1815
|
+
if (!error || typeof error !== "object") return false;
|
|
1816
|
+
const record = error;
|
|
1817
|
+
if (record.code === "invalid_encrypted_content" || record.code === "thinking_signature_invalid") return true;
|
|
1818
|
+
const message = typeof record.message === "string" ? record.message : "";
|
|
1819
|
+
return message.includes("invalid_encrypted_content") || message.includes("thinking_signature_invalid") || record.status === 400 && message.toLowerCase().includes("could not decrypt the provided encrypted_content");
|
|
1820
|
+
}
|
|
1821
|
+
function stripEncryptedContentFields(value) {
|
|
1822
|
+
if (!value || typeof value !== "object") return {
|
|
1823
|
+
value,
|
|
1824
|
+
changed: false
|
|
1825
|
+
};
|
|
1826
|
+
if (Array.isArray(value)) {
|
|
1827
|
+
let changed = false;
|
|
1828
|
+
const next = value.map((item) => {
|
|
1829
|
+
const stripped = stripEncryptedContentFields(item);
|
|
1830
|
+
changed ||= stripped.changed;
|
|
1831
|
+
return stripped.value;
|
|
1832
|
+
});
|
|
1833
|
+
return changed ? {
|
|
1834
|
+
value: next,
|
|
1835
|
+
changed: true
|
|
1836
|
+
} : {
|
|
1837
|
+
value,
|
|
1838
|
+
changed: false
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
let changed = false;
|
|
1842
|
+
const next = {};
|
|
1843
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1844
|
+
if (key === "encrypted_content") {
|
|
1845
|
+
changed = true;
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
const stripped = stripEncryptedContentFields(child);
|
|
1849
|
+
changed ||= stripped.changed;
|
|
1850
|
+
next[key] = stripped.value;
|
|
1851
|
+
}
|
|
1852
|
+
return changed ? {
|
|
1853
|
+
value: next,
|
|
1854
|
+
changed: true
|
|
1855
|
+
} : {
|
|
1856
|
+
value,
|
|
1857
|
+
changed: false
|
|
1858
|
+
};
|
|
1441
1859
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1860
|
+
function stripResponsesRequestEncryptedContent(params) {
|
|
1861
|
+
const stripped = stripEncryptedContentFields(params.input);
|
|
1862
|
+
if (!stripped.changed) return params;
|
|
1863
|
+
return {
|
|
1864
|
+
...params,
|
|
1865
|
+
input: stripped.value
|
|
1866
|
+
};
|
|
1445
1867
|
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
return
|
|
1449
|
-
toolIndex: diagnostic.toolIndex,
|
|
1450
|
-
...diagnostic.toolName ? { toolName: diagnostic.toolName } : {},
|
|
1451
|
-
violations: [...diagnostic.violations]
|
|
1452
|
-
})), ...projection.tools.flatMap((tool) => {
|
|
1453
|
-
const violations = findOpenAIStrictSchemaViolations(normalizeStrictOpenAIJsonSchema(tool.parameters), `${tool.name}.parameters`);
|
|
1454
|
-
return violations.length > 0 ? [{
|
|
1455
|
-
toolIndex: tool.toolIndex,
|
|
1456
|
-
toolName: tool.name,
|
|
1457
|
-
violations
|
|
1458
|
-
}] : [];
|
|
1459
|
-
})];
|
|
1868
|
+
function hashOptionalReplayContextValue(value) {
|
|
1869
|
+
const normalized = value?.trim();
|
|
1870
|
+
return normalized ? shortHash(normalized) : void 0;
|
|
1460
1871
|
}
|
|
1461
|
-
function
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
const required = Array.isArray(record.required) ? record.required.filter((entry) => typeof entry === "string") : void 0;
|
|
1471
|
-
if (!required) return false;
|
|
1472
|
-
const requiredSet = new Set(required);
|
|
1473
|
-
if (Object.keys(properties).some((key) => !requiredSet.has(key))) return false;
|
|
1474
|
-
}
|
|
1475
|
-
return Object.entries(record).every(([key, entry]) => {
|
|
1476
|
-
if (key === "properties" && entry && typeof entry === "object" && !Array.isArray(entry)) return Object.values(entry).every((value) => isStrictOpenAIJsonSchemaCompatibleRecursive(value));
|
|
1477
|
-
return isStrictOpenAIJsonSchemaCompatibleRecursive(entry);
|
|
1478
|
-
});
|
|
1872
|
+
function buildOpenAIResponsesReplayContext(model, options) {
|
|
1873
|
+
return {
|
|
1874
|
+
provider: model.provider,
|
|
1875
|
+
api: model.api,
|
|
1876
|
+
model: model.id,
|
|
1877
|
+
baseUrlHash: hashOptionalReplayContextValue(model.baseUrl),
|
|
1878
|
+
sessionHash: hashOptionalReplayContextValue(options?.sessionId),
|
|
1879
|
+
authProfileHash: hashOptionalReplayContextValue(options?.authProfileId)
|
|
1880
|
+
};
|
|
1479
1881
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1882
|
+
function buildOpenAIResponsesReasoningReplayMetadata(model, options) {
|
|
1883
|
+
return {
|
|
1884
|
+
v: 1,
|
|
1885
|
+
source: "openai-responses",
|
|
1886
|
+
...buildOpenAIResponsesReplayContext(model, options)
|
|
1887
|
+
};
|
|
1484
1888
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
const LOG_SUBSYSTEM = "llm/openai-responses";
|
|
1488
|
-
const MAX_STRICT_TOOL_DOWNGRADE_DIAGNOSTIC_KEYS = 64;
|
|
1489
|
-
const loggedStrictToolDowngradeDiagnosticKeys = /* @__PURE__ */ new Set();
|
|
1490
|
-
/** Converts and returns the projection used to reconcile tool choices. */
|
|
1491
|
-
function convertResponsesToolPayload(tools, options) {
|
|
1492
|
-
const projection = projectOpenAITools(tools);
|
|
1493
|
-
const strict = resolveResponsesStrictToolFlag(projection, resolveResponsesStrictToolSetting(options), options?.model);
|
|
1889
|
+
function tagOpenAIResponsesReasoningReplayItem(item, model, options) {
|
|
1890
|
+
if (!("encrypted_content" in item)) return item;
|
|
1494
1891
|
return {
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
const result = {
|
|
1498
|
-
type: "function",
|
|
1499
|
-
name: tool.name,
|
|
1500
|
-
description: tool.description,
|
|
1501
|
-
parameters: normalizeOpenAIStrictToolParameters(tool.parameters, strict === true, options?.model?.compat)
|
|
1502
|
-
};
|
|
1503
|
-
if (strict !== void 0) result.strict = strict;
|
|
1504
|
-
return result;
|
|
1505
|
-
})
|
|
1892
|
+
...item,
|
|
1893
|
+
[OPENAI_RESPONSES_REASONING_REPLAY_META_KEY]: buildOpenAIResponsesReasoningReplayMetadata(model, options)
|
|
1506
1894
|
};
|
|
1507
1895
|
}
|
|
1508
|
-
function
|
|
1509
|
-
if (
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
supportsStrictMode: options.supportsStrictMode
|
|
1513
|
-
});
|
|
1514
|
-
return false;
|
|
1896
|
+
function isOpenAIResponsesReasoningReplayMetadata(value) {
|
|
1897
|
+
if (!value || typeof value !== "object") return false;
|
|
1898
|
+
const record = value;
|
|
1899
|
+
return record.v === 1 && record.source === "openai-responses" && typeof record.provider === "string" && typeof record.api === "string" && typeof record.model === "string" && (record.baseUrlHash === void 0 || typeof record.baseUrlHash === "string") && (record.sessionHash === void 0 || typeof record.sessionHash === "string") && (record.authProfileHash === void 0 || typeof record.authProfileHash === "string");
|
|
1515
1900
|
}
|
|
1516
|
-
function
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
const diagnostics = findOpenAIStrictToolProjectionDiagnostics(projection);
|
|
1520
|
-
if (!shouldLogStrictToolDowngradeDiagnostic(diagnostics, model)) return null;
|
|
1521
|
-
const sample = diagnostics.slice(0, 5).map((entry) => ({
|
|
1522
|
-
tool: entry.toolName ?? `tool[${entry.toolIndex}]`,
|
|
1523
|
-
violations: entry.violations.slice(0, 8)
|
|
1524
|
-
}));
|
|
1525
|
-
return {
|
|
1526
|
-
message: `OpenAI responses tool schema strict mode downgraded to strict=false for ${model.provider ?? "unknown"}/${model.id ?? "unknown"} because ${diagnostics.length} tool schema(s) are not strict-compatible`,
|
|
1527
|
-
data: {
|
|
1528
|
-
provider: model.provider,
|
|
1529
|
-
model: model.id,
|
|
1530
|
-
incompatibleToolCount: diagnostics.length,
|
|
1531
|
-
sample
|
|
1532
|
-
}
|
|
1533
|
-
};
|
|
1534
|
-
});
|
|
1535
|
-
return strict;
|
|
1901
|
+
function encryptedReasoningReplayMetadataMatches(metadata, context) {
|
|
1902
|
+
if (!metadata) return false;
|
|
1903
|
+
return metadata.provider === context.provider && metadata.api === context.api && metadata.model === context.model && metadata.baseUrlHash === context.baseUrlHash && metadata.sessionHash === context.sessionHash && metadata.authProfileHash === context.authProfileHash;
|
|
1536
1904
|
}
|
|
1537
|
-
function
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
model: model.id,
|
|
1541
|
-
diagnostics: diagnostics.map((entry) => ({
|
|
1542
|
-
toolIndex: entry.toolIndex,
|
|
1543
|
-
toolName: entry.toolName ?? null,
|
|
1544
|
-
violations: entry.violations
|
|
1545
|
-
}))
|
|
1546
|
-
})).digest("hex");
|
|
1547
|
-
if (loggedStrictToolDowngradeDiagnosticKeys.has(key)) return false;
|
|
1548
|
-
if (loggedStrictToolDowngradeDiagnosticKeys.size >= MAX_STRICT_TOOL_DOWNGRADE_DIAGNOSTIC_KEYS) loggedStrictToolDowngradeDiagnosticKeys.clear();
|
|
1549
|
-
loggedStrictToolDowngradeDiagnosticKeys.add(key);
|
|
1550
|
-
return true;
|
|
1551
|
-
}
|
|
1552
|
-
function compareToolText(left, right) {
|
|
1553
|
-
const leftText = left ?? "";
|
|
1554
|
-
const rightText = right ?? "";
|
|
1555
|
-
if (leftText < rightText) return -1;
|
|
1556
|
-
if (leftText > rightText) return 1;
|
|
1557
|
-
return 0;
|
|
1558
|
-
}
|
|
1559
|
-
function sortResponsesToolsByName(tools) {
|
|
1560
|
-
return tools.toSorted((left, right) => compareToolText(left.name, right.name) || compareToolText(left.description, right.description));
|
|
1905
|
+
function readOpenAIResponsesReasoningReplayBlockMetadata(block) {
|
|
1906
|
+
const value = block[OPENAI_RESPONSES_REASONING_REPLAY_BLOCK_META_KEY];
|
|
1907
|
+
return isOpenAIResponsesReasoningReplayMetadata(value) ? value : void 0;
|
|
1561
1908
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1909
|
+
function normalizeOpenAIResponsesReasoningReplayItem(item) {
|
|
1910
|
+
const record = item;
|
|
1911
|
+
if (record.type !== "reasoning" || Array.isArray(record.summary)) return item;
|
|
1912
|
+
return {
|
|
1913
|
+
...record,
|
|
1914
|
+
summary: []
|
|
1915
|
+
};
|
|
1568
1916
|
}
|
|
1569
|
-
function
|
|
1570
|
-
const
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1917
|
+
function prepareOpenAIResponsesReasoningItemForReplay(item, context, blockMetadata) {
|
|
1918
|
+
const { [OPENAI_RESPONSES_REASONING_REPLAY_META_KEY]: rawMetadata, ...rest } = item;
|
|
1919
|
+
if (!("encrypted_content" in rest)) return normalizeOpenAIResponsesReasoningReplayItem(rest);
|
|
1920
|
+
if (encryptedReasoningReplayMetadataMatches(blockMetadata ?? (isOpenAIResponsesReasoningReplayMetadata(rawMetadata) ? rawMetadata : void 0), context)) return normalizeOpenAIResponsesReasoningReplayItem(rest);
|
|
1921
|
+
return normalizeOpenAIResponsesReasoningReplayItem(stripEncryptedContentFields(rest).value);
|
|
1922
|
+
}
|
|
1923
|
+
async function createResponsesStreamWithEncryptedContentRetry(params) {
|
|
1924
|
+
try {
|
|
1925
|
+
return await params.client.responses.create(params.request, params.requestOptions);
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
const retryRequest = stripResponsesRequestEncryptedContent(params.request);
|
|
1928
|
+
if (!isInvalidEncryptedContentError(error) || retryRequest === params.request) throw error;
|
|
1929
|
+
log.warn(`[responses] retrying without encrypted reasoning content provider=${params.model.provider} api=${params.model.api} model=${params.model.id}`);
|
|
1930
|
+
return await params.client.responses.create(retryRequest, params.requestOptions);
|
|
1931
|
+
}
|
|
1578
1932
|
}
|
|
1579
|
-
function
|
|
1580
|
-
|
|
1581
|
-
return sanitized.trim().length > 0 ? sanitized : fallback;
|
|
1933
|
+
function resolveAzureOpenAIApiVersion(env = process.env) {
|
|
1934
|
+
return env.AZURE_OPENAI_API_VERSION?.trim() || "preview";
|
|
1582
1935
|
}
|
|
1583
|
-
function
|
|
1584
|
-
|
|
1585
|
-
if (
|
|
1586
|
-
|
|
1587
|
-
|
|
1936
|
+
function normalizeResponsesReplayItemId(id, prefix) {
|
|
1937
|
+
if (!id) return;
|
|
1938
|
+
if (id.length <= 64) return id;
|
|
1939
|
+
return `${prefix}_${shortHash(id)}`;
|
|
1940
|
+
}
|
|
1941
|
+
function isSafeResponsesReplayItemId(id) {
|
|
1942
|
+
return typeof id === "string" && id.length > 0 && id.length <= 64;
|
|
1588
1943
|
}
|
|
1589
1944
|
function encodeTextSignatureV1(id, phase) {
|
|
1590
|
-
|
|
1945
|
+
return JSON.stringify({
|
|
1591
1946
|
v: 1,
|
|
1592
|
-
id
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
return JSON.stringify(payload);
|
|
1947
|
+
id,
|
|
1948
|
+
...phase ? { phase } : {}
|
|
1949
|
+
});
|
|
1596
1950
|
}
|
|
1597
1951
|
function parseTextSignature(signature) {
|
|
1598
1952
|
if (!signature) return;
|
|
@@ -1610,339 +1964,572 @@ function parseTextSignature(signature) {
|
|
|
1610
1964
|
} catch {}
|
|
1611
1965
|
return { id: signature };
|
|
1612
1966
|
}
|
|
1613
|
-
function
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1967
|
+
function buildResponsesInputMessage(role, content) {
|
|
1968
|
+
return {
|
|
1969
|
+
type: "message",
|
|
1970
|
+
role,
|
|
1971
|
+
content
|
|
1972
|
+
};
|
|
1619
1973
|
}
|
|
1620
1974
|
function convertResponsesMessages(model, context, allowedToolCallProviders, options) {
|
|
1621
1975
|
const messages = [];
|
|
1976
|
+
const shouldReplayReasoningItems = options?.replayReasoningItems ?? true;
|
|
1622
1977
|
const shouldReplayResponsesItemIds = options?.replayResponsesItemIds ?? true;
|
|
1978
|
+
const replayContext = buildOpenAIResponsesReplayContext(model, {
|
|
1979
|
+
sessionId: options?.sessionId,
|
|
1980
|
+
authProfileId: options?.authProfileId
|
|
1981
|
+
});
|
|
1982
|
+
const shouldNormalizeSameModelToolCallIds = model.provider === "github-copilot";
|
|
1983
|
+
const sanitizeIdPart = (part) => part.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+$/, "");
|
|
1623
1984
|
const normalizeIdPart = (part) => {
|
|
1624
|
-
const sanitized = part
|
|
1985
|
+
const sanitized = sanitizeIdPart(part);
|
|
1625
1986
|
return (sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized).replace(/_+$/, "");
|
|
1626
1987
|
};
|
|
1627
1988
|
const buildForeignResponsesItemId = (itemId) => {
|
|
1628
1989
|
const normalized = `fc_${shortHash(itemId)}`;
|
|
1629
1990
|
return normalized.length > 64 ? normalized.slice(0, 64) : normalized;
|
|
1630
1991
|
};
|
|
1631
|
-
const
|
|
1992
|
+
const buildSameProviderCopilotResponsesItemId = (itemId) => {
|
|
1993
|
+
const sanitized = sanitizeIdPart(itemId);
|
|
1994
|
+
const candidate = sanitized.startsWith("fc_") ? sanitized : `fc_${sanitized}`;
|
|
1995
|
+
return candidate.length > 64 ? buildForeignResponsesItemId(itemId) : candidate;
|
|
1996
|
+
};
|
|
1997
|
+
const normalizeToolCallId = (id, _targetModel, source) => {
|
|
1632
1998
|
if (!allowedToolCallProviders.has(model.provider)) return normalizeIdPart(id);
|
|
1633
1999
|
if (!id.includes("|")) return normalizeIdPart(id);
|
|
1634
|
-
const
|
|
2000
|
+
const separatorIndex = id.indexOf("|");
|
|
2001
|
+
const callId = id.slice(0, separatorIndex);
|
|
2002
|
+
const itemId = id.slice(separatorIndex + 1);
|
|
1635
2003
|
const normalizedCallId = normalizeIdPart(callId);
|
|
1636
|
-
let normalizedItemId = source.provider !== model.provider || source.api !== model.api ? buildForeignResponsesItemId(itemId) : normalizeIdPart(itemId);
|
|
2004
|
+
let normalizedItemId = source.provider !== model.provider || source.api !== model.api ? buildForeignResponsesItemId(itemId) : model.provider === "github-copilot" ? buildSameProviderCopilotResponsesItemId(itemId) : normalizeIdPart(itemId);
|
|
1637
2005
|
if (!normalizedItemId.startsWith("fc_")) normalizedItemId = normalizeIdPart(`fc_${normalizedItemId}`);
|
|
1638
2006
|
return `${normalizedCallId}|${normalizedItemId}`;
|
|
1639
2007
|
};
|
|
1640
|
-
const transformedMessages =
|
|
1641
|
-
if ((options?.includeSystemPrompt ?? true) && context.systemPrompt) {
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
type: "message",
|
|
1646
|
-
role,
|
|
1647
|
-
content: [{
|
|
1648
|
-
type: "input_text",
|
|
1649
|
-
text: sanitizeSurrogates(stripSystemPromptCacheBoundary(context.systemPrompt))
|
|
1650
|
-
}]
|
|
1651
|
-
});
|
|
1652
|
-
}
|
|
2008
|
+
const transformedMessages = transformTransportMessages(context.messages, model, normalizeToolCallId, { normalizeSameModelToolCallIds: shouldNormalizeSameModelToolCallIds });
|
|
2009
|
+
if ((options?.includeSystemPrompt ?? true) && context.systemPrompt) messages.push(buildResponsesInputMessage(model.reasoning && options?.supportsDeveloperRole !== false ? "developer" : "system", [{
|
|
2010
|
+
type: "input_text",
|
|
2011
|
+
text: sanitizeTransportPayloadText(stripSystemPromptCacheBoundary(context.systemPrompt))
|
|
2012
|
+
}]));
|
|
1653
2013
|
let msgIndex = 0;
|
|
1654
2014
|
for (const msg of transformedMessages) {
|
|
1655
|
-
if (msg.role === "user") if (typeof msg.content === "string") messages.push({
|
|
1656
|
-
type: "
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
type: "input_text",
|
|
1660
|
-
text: sanitizeSurrogates(msg.content)
|
|
1661
|
-
}]
|
|
1662
|
-
});
|
|
2015
|
+
if (msg.role === "user") if (typeof msg.content === "string") messages.push(buildResponsesInputMessage("user", [{
|
|
2016
|
+
type: "input_text",
|
|
2017
|
+
text: sanitizeTransportPayloadText(msg.content)
|
|
2018
|
+
}]));
|
|
1663
2019
|
else {
|
|
1664
|
-
const content = msg.content.map((item) => {
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
};
|
|
1674
|
-
});
|
|
1675
|
-
if (content.length === 0) continue;
|
|
1676
|
-
messages.push({
|
|
1677
|
-
type: "message",
|
|
1678
|
-
role: "user",
|
|
1679
|
-
content
|
|
1680
|
-
});
|
|
2020
|
+
const content = msg.content.map((item) => item.type === "text" ? {
|
|
2021
|
+
type: "input_text",
|
|
2022
|
+
text: sanitizeTransportPayloadText(item.text)
|
|
2023
|
+
} : {
|
|
2024
|
+
type: "input_image",
|
|
2025
|
+
detail: "auto",
|
|
2026
|
+
image_url: `data:${item.mimeType};base64,${item.data}`
|
|
2027
|
+
}).filter((item) => model.input.includes("image") || item.type !== "input_image");
|
|
2028
|
+
if (content.length > 0) messages.push(buildResponsesInputMessage("user", content));
|
|
1681
2029
|
}
|
|
1682
2030
|
else if (msg.role === "assistant") {
|
|
1683
2031
|
const output = [];
|
|
1684
2032
|
let textFallbackOrdinal = 0;
|
|
1685
|
-
const assistantMsg = msg;
|
|
1686
2033
|
let previousReplayItemWasReasoning = false;
|
|
1687
|
-
const isDifferentModel =
|
|
2034
|
+
const isDifferentModel = msg.model !== model.id && msg.provider === model.provider && msg.api === model.api;
|
|
1688
2035
|
for (const block of msg.content) if (block.type === "thinking") {
|
|
1689
|
-
if (block.thinkingSignature) {
|
|
1690
|
-
const
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
output.push(reasoningItem);
|
|
2036
|
+
if (shouldReplayReasoningItems && block.thinkingSignature && block.thinkingSignature.startsWith("{")) {
|
|
2037
|
+
const replayableReasoningItem = prepareOpenAIResponsesReasoningItemForReplay(JSON.parse(block.thinkingSignature), replayContext, readOpenAIResponsesReasoningReplayBlockMetadata(block));
|
|
2038
|
+
if (!shouldReplayResponsesItemIds) delete replayableReasoningItem.id;
|
|
2039
|
+
if (shouldReplayResponsesItemIds && model.provider === "github-copilot" && !isSafeResponsesReplayItemId(replayableReasoningItem.id)) continue;
|
|
2040
|
+
output.push(replayableReasoningItem);
|
|
1695
2041
|
previousReplayItemWasReasoning = true;
|
|
1696
2042
|
}
|
|
1697
2043
|
} else if (block.type === "text") {
|
|
1698
|
-
const
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
textSignatureId:
|
|
2044
|
+
const textSignature = parseTextSignature(block.textSignature);
|
|
2045
|
+
let msgId = resolveReplayableResponsesMessageId({
|
|
2046
|
+
replayResponsesItemIds: shouldReplayResponsesItemIds,
|
|
2047
|
+
textSignatureId: textSignature?.id,
|
|
1702
2048
|
fallbackId: `msg_${msgIndex}`,
|
|
1703
2049
|
fallbackOrdinal: textFallbackOrdinal,
|
|
1704
2050
|
previousReplayItemWasReasoning
|
|
1705
|
-
})
|
|
1706
|
-
if (!
|
|
1707
|
-
|
|
2051
|
+
});
|
|
2052
|
+
if (!textSignature?.id) textFallbackOrdinal += 1;
|
|
2053
|
+
msgId = normalizeResponsesReplayItemId(msgId, "msg");
|
|
1708
2054
|
const messageItem = {
|
|
1709
2055
|
type: "message",
|
|
1710
2056
|
role: "assistant",
|
|
1711
2057
|
content: [{
|
|
1712
2058
|
type: "output_text",
|
|
1713
|
-
text:
|
|
2059
|
+
text: sanitizeTransportPayloadText(block.text),
|
|
1714
2060
|
annotations: []
|
|
1715
2061
|
}],
|
|
1716
2062
|
status: "completed",
|
|
1717
2063
|
...msgId ? { id: msgId } : {},
|
|
1718
|
-
phase:
|
|
2064
|
+
phase: textSignature?.phase
|
|
1719
2065
|
};
|
|
1720
2066
|
output.push(messageItem);
|
|
1721
2067
|
previousReplayItemWasReasoning = false;
|
|
1722
2068
|
} else if (block.type === "toolCall") {
|
|
1723
|
-
const
|
|
1724
|
-
const
|
|
1725
|
-
|
|
1726
|
-
|
|
2069
|
+
const separatorIndex = block.id.indexOf("|");
|
|
2070
|
+
const callId = separatorIndex === -1 ? block.id : block.id.slice(0, separatorIndex);
|
|
2071
|
+
const itemIdRaw = separatorIndex === -1 ? void 0 : block.id.slice(separatorIndex + 1);
|
|
2072
|
+
const itemId = shouldReplayResponsesItemIds && !(isDifferentModel && itemIdRaw?.startsWith("fc_")) ? itemIdRaw : void 0;
|
|
1727
2073
|
output.push({
|
|
1728
2074
|
type: "function_call",
|
|
1729
2075
|
...itemId ? { id: itemId } : {},
|
|
1730
2076
|
call_id: callId,
|
|
1731
|
-
name:
|
|
1732
|
-
arguments: JSON.stringify(
|
|
2077
|
+
name: block.name,
|
|
2078
|
+
arguments: typeof block.arguments === "string" ? block.arguments : JSON.stringify(block.arguments ?? {})
|
|
1733
2079
|
});
|
|
1734
2080
|
previousReplayItemWasReasoning = false;
|
|
1735
2081
|
}
|
|
1736
|
-
if (output.length
|
|
1737
|
-
messages.push(...output);
|
|
2082
|
+
if (output.length > 0) messages.push(...output);
|
|
1738
2083
|
} else if (msg.role === "toolResult") {
|
|
1739
2084
|
const textResult = extractToolResultText(msg.content);
|
|
1740
|
-
const sanitizedTextResult =
|
|
1741
|
-
const hasImages = msg.content.some(isImageWithMediaPayload);
|
|
1742
|
-
const mediaPlaceholder = describeToolResultMediaPlaceholder(msg.content);
|
|
2085
|
+
const sanitizedTextResult = sanitizeTransportPayloadText(textResult);
|
|
1743
2086
|
const hasText = sanitizedTextResult.trim().length > 0;
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
2087
|
+
const mediaPlaceholder = describeToolResultMediaPlaceholder(msg.content);
|
|
2088
|
+
const hasImages = msg.content.some(isImageWithMediaPayload);
|
|
2089
|
+
const separatorIndex = msg.toolCallId.indexOf("|");
|
|
2090
|
+
const callId = separatorIndex === -1 ? msg.toolCallId : msg.toolCallId.slice(0, separatorIndex);
|
|
2091
|
+
messages.push({
|
|
2092
|
+
type: "function_call_output",
|
|
2093
|
+
call_id: callId,
|
|
2094
|
+
output: hasImages && model.input.includes("image") ? [...hasText ? [{
|
|
1749
2095
|
type: "input_text",
|
|
1750
2096
|
text: sanitizedTextResult
|
|
1751
|
-
})
|
|
1752
|
-
else if (mediaPlaceholder === "(see attached media)") contentParts.push({
|
|
2097
|
+
}] : mediaPlaceholder === "(see attached media)" ? [{
|
|
1753
2098
|
type: "input_text",
|
|
1754
2099
|
text: mediaPlaceholder
|
|
1755
|
-
})
|
|
1756
|
-
for (const block of msg.content) if (isImageWithMediaPayload(block)) contentParts.push({
|
|
2100
|
+
}] : [], ...msg.content.filter(isImageWithMediaPayload).map((item) => ({
|
|
1757
2101
|
type: "input_image",
|
|
1758
2102
|
detail: "auto",
|
|
1759
|
-
image_url: `data:${
|
|
1760
|
-
})
|
|
1761
|
-
output = contentParts;
|
|
1762
|
-
} else output = sanitizeToolResultText(textResult, mediaPlaceholder ?? EMPTY_TOOL_RESULT_TEXT);
|
|
1763
|
-
messages.push({
|
|
1764
|
-
type: "function_call_output",
|
|
1765
|
-
call_id: callId,
|
|
1766
|
-
output
|
|
2103
|
+
image_url: `data:${item.mimeType};base64,${item.data}`
|
|
2104
|
+
}))] : sanitizeNonEmptyTransportPayloadText(textResult, mediaPlaceholder ?? "(no output)")
|
|
1767
2105
|
});
|
|
1768
2106
|
}
|
|
1769
|
-
msgIndex
|
|
2107
|
+
msgIndex += 1;
|
|
1770
2108
|
}
|
|
1771
2109
|
return messages;
|
|
1772
2110
|
}
|
|
1773
|
-
|
|
2111
|
+
//#endregion
|
|
2112
|
+
//#region packages/ai/src/providers/openai-responses-stream-compat.ts
|
|
2113
|
+
const OPENAI_RESPONSES_OUTPUT_TEXT_CONTENT_PART_TYPE = "output_text";
|
|
2114
|
+
const AZURE_RESPONSES_TEXT_CONTENT_PART_TYPE = "text";
|
|
2115
|
+
const OPENAI_RESPONSES_OUTPUT_TEXT_DELTA_EVENT_TYPE = "response.output_text.delta";
|
|
2116
|
+
const AZURE_RESPONSES_TEXT_DELTA_EVENT_TYPE = "response.text.delta";
|
|
2117
|
+
function isResponsesTextContentPartType(type) {
|
|
2118
|
+
return type === "output_text" || type === "text";
|
|
2119
|
+
}
|
|
2120
|
+
function isResponsesTextDeltaEventType(type) {
|
|
2121
|
+
return type === "response.output_text.delta" || type === "response.text.delta";
|
|
2122
|
+
}
|
|
2123
|
+
function isAzureResponsesTextDeltaEventType(type) {
|
|
2124
|
+
return type === AZURE_RESPONSES_TEXT_DELTA_EVENT_TYPE;
|
|
2125
|
+
}
|
|
2126
|
+
function isAzureResponsesTextDeltaEvent(event) {
|
|
2127
|
+
return isAzureResponsesTextDeltaEventType(event.type) && typeof event.delta === "string";
|
|
2128
|
+
}
|
|
2129
|
+
function resolveResponsesMessageSnapshotCollapse(params) {
|
|
2130
|
+
const { prior, nextText } = params;
|
|
2131
|
+
if (!prior?.text || !nextText || prior.phase !== params.nextPhase) return { kind: "keep" };
|
|
2132
|
+
if (nextText.length > prior.text.length && nextText.startsWith(prior.text)) return {
|
|
2133
|
+
kind: "extend",
|
|
2134
|
+
text: nextText
|
|
2135
|
+
};
|
|
2136
|
+
return { kind: "keep" };
|
|
2137
|
+
}
|
|
2138
|
+
//#endregion
|
|
2139
|
+
//#region packages/ai/src/providers/openai-responses-tool-call-tracker.ts
|
|
2140
|
+
function readIdentityValue(value) {
|
|
2141
|
+
return (typeof value === "string" ? value.trim() : "") || void 0;
|
|
2142
|
+
}
|
|
2143
|
+
function readOutputIndex(event) {
|
|
2144
|
+
return typeof event.output_index === "number" && Number.isInteger(event.output_index) && event.output_index >= 0 ? event.output_index : void 0;
|
|
2145
|
+
}
|
|
2146
|
+
function readEventIdentity(event) {
|
|
2147
|
+
return { itemId: readIdentityValue(event.item_id) };
|
|
2148
|
+
}
|
|
2149
|
+
function readResponsesToolCallItemIdentity(item) {
|
|
1774
2150
|
return {
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2151
|
+
itemId: readIdentityValue(item.id),
|
|
2152
|
+
callId: readIdentityValue(item.call_id)
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
function createResponsesToolCallTracker() {
|
|
2156
|
+
const indexedCalls = /* @__PURE__ */ new Map();
|
|
2157
|
+
const unindexedCalls = /* @__PURE__ */ new Set();
|
|
2158
|
+
const identitiesConflict = (state, identity) => Boolean(state.itemId && identity.itemId && state.itemId !== identity.itemId || state.callId && identity.callId && state.callId !== identity.callId);
|
|
2159
|
+
const sharesIdentity = (state, identity) => Boolean(state.itemId && identity.itemId && state.itemId === identity.itemId || state.callId && identity.callId && state.callId === identity.callId);
|
|
2160
|
+
const adoptIdentity = (state, identity) => {
|
|
2161
|
+
state.itemId ??= identity.itemId;
|
|
2162
|
+
state.callId ??= identity.callId;
|
|
2163
|
+
return state;
|
|
2164
|
+
};
|
|
2165
|
+
const resolveCompatible = (candidates, identity) => {
|
|
2166
|
+
const uniqueCandidates = [...new Set(candidates)];
|
|
2167
|
+
if (!identity.itemId && !identity.callId) return uniqueCandidates.length === 1 ? uniqueCandidates.at(0) : void 0;
|
|
2168
|
+
const compatible = uniqueCandidates.filter((state) => !identitiesConflict(state, identity));
|
|
2169
|
+
const matches = compatible.filter((state) => sharesIdentity(state, identity));
|
|
2170
|
+
const matched = matches.length === 1 ? matches.at(0) : void 0;
|
|
2171
|
+
if (matched) return adoptIdentity(matched, identity);
|
|
2172
|
+
const soleCompatible = uniqueCandidates.length === 1 && compatible.length === 1 && matches.length === 0 ? compatible.at(0) : void 0;
|
|
2173
|
+
return soleCompatible ? adoptIdentity(soleCompatible, identity) : void 0;
|
|
2174
|
+
};
|
|
2175
|
+
return {
|
|
2176
|
+
register(event, state) {
|
|
2177
|
+
const outputIndex = readOutputIndex(event);
|
|
2178
|
+
if (outputIndex === void 0) {
|
|
2179
|
+
unindexedCalls.add(state);
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
if (indexedCalls.has(outputIndex)) throw new Error(`Responses stream reused active tool-call output index ${outputIndex}`);
|
|
2183
|
+
indexedCalls.set(outputIndex, state);
|
|
2184
|
+
},
|
|
2185
|
+
resolve(event, identity = readEventIdentity(event)) {
|
|
2186
|
+
const outputIndex = readOutputIndex(event);
|
|
2187
|
+
if (outputIndex !== void 0) {
|
|
2188
|
+
const indexed = indexedCalls.get(outputIndex);
|
|
2189
|
+
if (indexed) {
|
|
2190
|
+
if (indexed.callId && identity.callId && indexed.callId !== identity.callId) return;
|
|
2191
|
+
return adoptIdentity(indexed, identity);
|
|
2192
|
+
}
|
|
2193
|
+
const unindexed = resolveCompatible(unindexedCalls, identity);
|
|
2194
|
+
if (unindexed) {
|
|
2195
|
+
unindexedCalls.delete(unindexed);
|
|
2196
|
+
indexedCalls.set(outputIndex, unindexed);
|
|
2197
|
+
}
|
|
2198
|
+
return unindexed;
|
|
2199
|
+
}
|
|
2200
|
+
return resolveCompatible([...indexedCalls.values(), ...unindexedCalls], identity);
|
|
2201
|
+
},
|
|
2202
|
+
forget(toolCall) {
|
|
2203
|
+
for (const [outputIndex, tracked] of indexedCalls) if (tracked === toolCall) indexedCalls.delete(outputIndex);
|
|
2204
|
+
unindexedCalls.delete(toolCall);
|
|
2205
|
+
},
|
|
2206
|
+
markArgumentsUnreliable() {
|
|
2207
|
+
for (const toolCall of /* @__PURE__ */ new Set([...indexedCalls.values(), ...unindexedCalls])) toolCall.argumentStreamReliable = false;
|
|
2208
|
+
},
|
|
2209
|
+
hasActive() {
|
|
2210
|
+
return indexedCalls.size > 0 || unindexedCalls.size > 0;
|
|
2211
|
+
}
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
//#endregion
|
|
2215
|
+
//#region packages/ai/src/transports/openai-responses-stream-observer-internal.ts
|
|
2216
|
+
const STRING_DELTA_EVENTS = /* @__PURE__ */ new Set([
|
|
2217
|
+
"response.function_call_arguments.delta",
|
|
2218
|
+
"response.output_text.delta",
|
|
2219
|
+
"response.reasoning_summary_text.delta",
|
|
2220
|
+
"response.reasoning_text.delta",
|
|
2221
|
+
"response.refusal.delta",
|
|
2222
|
+
"response.text.delta"
|
|
2223
|
+
]);
|
|
2224
|
+
async function* adaptResponsesStream(stream, signal) {
|
|
2225
|
+
const scheduler = createModelStreamCooperativeScheduler(signal);
|
|
2226
|
+
for await (const event of stream) {
|
|
2227
|
+
if (signal?.aborted) throw transportAbortError(signal);
|
|
2228
|
+
if (!isRecord(event) || typeof event.type !== "string") throw new Error("Responses stream delivered a malformed event without a string type");
|
|
2229
|
+
if (STRING_DELTA_EVENTS.has(event.type) && typeof event.delta !== "string") throw new Error(`Responses stream delivered malformed ${event.type} delta`);
|
|
2230
|
+
if ((event.type === "response.output_item.added" || event.type === "response.output_item.done") && !isRecord(event.item)) throw new Error(`Responses stream delivered malformed ${event.type} item`);
|
|
2231
|
+
if ((event.type === "response.created" || event.type === "response.completed" || event.type === "response.incomplete" || event.type === "response.failed") && !isRecord(event.response)) throw new Error(`Responses stream delivered malformed ${event.type} response`);
|
|
2232
|
+
yield event;
|
|
2233
|
+
await scheduler.afterEvent();
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
async function* observeResponsesStream(stream, model) {
|
|
2237
|
+
const startedAt = Date.now();
|
|
2238
|
+
const eventTypes = /* @__PURE__ */ new Map();
|
|
2239
|
+
const debugMode = resolveModelSseDebugMode();
|
|
2240
|
+
let eventCount = 0;
|
|
2241
|
+
try {
|
|
2242
|
+
for await (const event of stream) {
|
|
2243
|
+
const type = isRecord(event) && typeof event.type === "string" ? event.type : "unknown";
|
|
2244
|
+
eventCount += 1;
|
|
2245
|
+
eventTypes.set(type, (eventTypes.get(type) ?? 0) + 1);
|
|
2246
|
+
if (eventCount === 1) emitModelTransportDebug(log, `[responses] first_event provider=${model.provider} api=${model.api} model=${model.id} elapsedMs=${Date.now() - startedAt} type=${type}`);
|
|
2247
|
+
if (debugMode === "peek" && eventCount <= 5) emitModelTransportDebug(log, `[responses] event_peek provider=${model.provider} api=${model.api} model=${model.id} index=${eventCount} type=${type} event=${stringifyRedactedEvent(event)}`);
|
|
2248
|
+
yield event;
|
|
2249
|
+
}
|
|
2250
|
+
} finally {
|
|
2251
|
+
const types = [...eventTypes].map(([type, count]) => `${type}:${count}`).join(",");
|
|
2252
|
+
emitModelTransportDebug(log, `[responses] stream_done provider=${model.provider} api=${model.api} model=${model.id} elapsedMs=${Date.now() - startedAt} events=${eventCount} types=${types}`);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
//#endregion
|
|
2256
|
+
//#region packages/ai/src/transports/openai-responses-stream-slots-internal.ts
|
|
2257
|
+
function readResponsesOutputIndex(event) {
|
|
2258
|
+
const outputIndex = event.output_index;
|
|
2259
|
+
return typeof outputIndex === "number" && Number.isInteger(outputIndex) && outputIndex >= 0 ? outputIndex : void 0;
|
|
2260
|
+
}
|
|
2261
|
+
function createResponsesOutputSlotTracker() {
|
|
2262
|
+
const indexed = /* @__PURE__ */ new Map();
|
|
2263
|
+
let unindexed;
|
|
2264
|
+
return {
|
|
2265
|
+
register(event, slot) {
|
|
2266
|
+
const outputIndex = readResponsesOutputIndex(event);
|
|
2267
|
+
if (outputIndex === void 0) {
|
|
2268
|
+
if (unindexed) throw new Error("Responses stream added overlapping unindexed output items");
|
|
2269
|
+
unindexed = slot;
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
if (indexed.has(outputIndex)) throw new Error(`Responses stream reused active output index ${outputIndex}`);
|
|
2273
|
+
indexed.set(outputIndex, slot);
|
|
2274
|
+
},
|
|
2275
|
+
resolve(event, type) {
|
|
2276
|
+
const outputIndex = readResponsesOutputIndex(event);
|
|
2277
|
+
let slot = outputIndex === void 0 ? unindexed : indexed.get(outputIndex);
|
|
2278
|
+
if (outputIndex === void 0 && !slot) {
|
|
2279
|
+
const matches = [...indexed.values()].filter((candidate) => candidate.type === type);
|
|
2280
|
+
slot = matches.length === 1 ? matches[0] : void 0;
|
|
1792
2281
|
}
|
|
2282
|
+
return slot?.type === type ? slot : void 0;
|
|
2283
|
+
},
|
|
2284
|
+
get(event) {
|
|
2285
|
+
const outputIndex = readResponsesOutputIndex(event);
|
|
2286
|
+
return outputIndex === void 0 ? unindexed : indexed.get(outputIndex);
|
|
1793
2287
|
},
|
|
1794
|
-
|
|
1795
|
-
|
|
2288
|
+
values() {
|
|
2289
|
+
return [.../* @__PURE__ */ new Set([...indexed.values(), ...unindexed ? [unindexed] : []])];
|
|
2290
|
+
},
|
|
2291
|
+
forget(slot) {
|
|
2292
|
+
if (unindexed === slot) unindexed = void 0;
|
|
2293
|
+
for (const [outputIndex, candidate] of indexed) if (candidate === slot) indexed.delete(outputIndex);
|
|
2294
|
+
}
|
|
1796
2295
|
};
|
|
1797
2296
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
if (clampedReasoning === "minimal" && model.provider === "openai" && supportsOpenAIReasoningEffort(model, "max")) {
|
|
1803
|
-
const effort = resolveOpenAIReasoningEffortForModel({
|
|
1804
|
-
model,
|
|
1805
|
-
effort: "minimal"
|
|
1806
|
-
});
|
|
1807
|
-
return isResponsesReasoningEffort(effort) ? effort : void 0;
|
|
1808
|
-
}
|
|
1809
|
-
return clampedReasoning;
|
|
1810
|
-
}
|
|
1811
|
-
function applyCommonResponsesParams(params, model, context, options, config) {
|
|
1812
|
-
if (options?.maxTokens) params.max_output_tokens = Math.max(options.maxTokens, 16);
|
|
1813
|
-
if (options?.temperature !== void 0 && supportsOpenAITemperature(model)) params.temperature = options.temperature;
|
|
1814
|
-
if (context.tools) {
|
|
1815
|
-
const converted = convertResponsesToolPayload(context.tools, { model });
|
|
1816
|
-
if (converted.tools.length > 0) params.tools = converted.tools;
|
|
1817
|
-
}
|
|
1818
|
-
if (!model.reasoning) return;
|
|
1819
|
-
if (options?.reasoningEffort || options?.reasoningSummary) {
|
|
1820
|
-
params.reasoning = {
|
|
1821
|
-
effort: options?.reasoningEffort ? model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort : "medium",
|
|
1822
|
-
summary: options?.reasoningSummary || "auto"
|
|
1823
|
-
};
|
|
1824
|
-
params.include = ["reasoning.encrypted_content"];
|
|
1825
|
-
} else if ((config?.setDefaultReasoningOff ?? true) && model.thinkingLevelMap?.off !== null) params.reasoning = { effort: model.thinkingLevelMap?.off ?? "none" };
|
|
2297
|
+
//#endregion
|
|
2298
|
+
//#region packages/ai/src/providers/openai-responses-terminal-usage.ts
|
|
2299
|
+
function readCount(value) {
|
|
2300
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1826
2301
|
}
|
|
1827
|
-
|
|
2302
|
+
/**
|
|
2303
|
+
* Split a terminal usage payload into the priced buckets.
|
|
2304
|
+
*
|
|
2305
|
+
* OpenAI includes cache reads and writes in `input_tokens`, so both are subtracted out of the
|
|
2306
|
+
* billable input bucket. `total_tokens` comes from the payload, but never below the sum of the
|
|
2307
|
+
* split buckets: proxies routinely omit it (reporting 0 would understate the turn), and a payload
|
|
2308
|
+
* whose `cached_tokens` exceeds `input_tokens` clamps the input bucket, leaving the reported total
|
|
2309
|
+
* short of what the buckets actually price.
|
|
2310
|
+
*/
|
|
2311
|
+
function mapResponsesTerminalUsage(usage) {
|
|
2312
|
+
if (!usage) return;
|
|
2313
|
+
const cacheRead = readCount(usage.input_tokens_details?.cached_tokens);
|
|
2314
|
+
const cacheWrite = readCount(usage.input_tokens_details?.cache_write_tokens);
|
|
2315
|
+
const input = Math.max(0, readCount(usage.input_tokens) - cacheRead - cacheWrite);
|
|
2316
|
+
const output = readCount(usage.output_tokens);
|
|
2317
|
+
const bucketTotal = input + output + cacheRead + cacheWrite;
|
|
1828
2318
|
return {
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
2319
|
+
input,
|
|
2320
|
+
output,
|
|
2321
|
+
cacheRead,
|
|
2322
|
+
cacheWrite,
|
|
2323
|
+
totalTokens: Math.max(bucketTotal, readCount(usage.total_tokens))
|
|
1832
2324
|
};
|
|
1833
2325
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2326
|
+
/** Reasoning tokens are reported by the agent path only; the package path does not track them. */
|
|
2327
|
+
function readResponsesReasoningTokens(usage) {
|
|
2328
|
+
const reasoningTokens = usage?.output_tokens_details?.reasoning_tokens;
|
|
2329
|
+
return typeof reasoningTokens === "number" && Number.isFinite(reasoningTokens) ? reasoningTokens : void 0;
|
|
2330
|
+
}
|
|
2331
|
+
function mapResponsesTerminalStopReason(status) {
|
|
2332
|
+
if (!status) return "stop";
|
|
2333
|
+
switch (status) {
|
|
2334
|
+
case "completed": return "stop";
|
|
2335
|
+
case "incomplete": return "length";
|
|
2336
|
+
case "failed":
|
|
2337
|
+
case "cancelled": return "error";
|
|
2338
|
+
case "in_progress":
|
|
2339
|
+
case "queued": return "stop";
|
|
2340
|
+
default: throw new Error(`Unhandled stop reason: ${String(status)}`);
|
|
1838
2341
|
}
|
|
1839
2342
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
2343
|
+
/**
|
|
2344
|
+
* Resolve the terminal stop reason, including the two overrides every Responses path shares: a
|
|
2345
|
+
* content-filtered turn is a provider error rather than a truncated answer, and a turn that
|
|
2346
|
+
* produced tool calls reports `toolUse` instead of a plain stop.
|
|
2347
|
+
*/
|
|
2348
|
+
function resolveResponsesTerminalStopReason(params) {
|
|
2349
|
+
const status = params.status ?? (params.terminalEventType === "response.incomplete" ? "incomplete" : void 0);
|
|
2350
|
+
if (status === "incomplete" && params.incompleteReason === "content_filter") return {
|
|
2351
|
+
stopReason: "error",
|
|
2352
|
+
errorMessage: "Provider incomplete_reason: content_filter"
|
|
2353
|
+
};
|
|
2354
|
+
const stopReason = mapResponsesTerminalStopReason(status);
|
|
2355
|
+
if (stopReason === "stop" && params.hasToolCall) return { stopReason: "toolUse" };
|
|
2356
|
+
return { stopReason };
|
|
2357
|
+
}
|
|
2358
|
+
//#endregion
|
|
2359
|
+
//#region packages/ai/src/transports/openai-responses-stream-terminal-internal.ts
|
|
2360
|
+
function splitToolCallId(id) {
|
|
2361
|
+
const separator = id.indexOf("|");
|
|
2362
|
+
return separator === -1 ? [id, void 0] : [id.slice(0, separator), id.slice(separator + 1)];
|
|
2363
|
+
}
|
|
2364
|
+
function resolveResponsesToolCallId(item, fallbackId) {
|
|
2365
|
+
const callId = typeof item.call_id === "string" ? item.call_id.trim() : "";
|
|
2366
|
+
const itemId = typeof item.id === "string" ? item.id.trim() : "";
|
|
2367
|
+
const [fallbackCallId, fallbackItemId = ""] = splitToolCallId(fallbackId ?? "");
|
|
2368
|
+
const resolvedCallId = callId || fallbackCallId;
|
|
2369
|
+
const resolvedItemId = itemId || fallbackItemId;
|
|
2370
|
+
if (resolvedCallId) return resolvedItemId ? `${resolvedCallId}|${resolvedItemId}` : resolvedCallId;
|
|
2371
|
+
const generated = `call_${randomUUID().replaceAll("-", "").slice(0, 24)}`;
|
|
2372
|
+
return resolvedItemId ? `${generated}|${resolvedItemId}` : generated;
|
|
2373
|
+
}
|
|
2374
|
+
function resolveCompletedToolCallName(toolCall, value) {
|
|
2375
|
+
const streamedName = toolCall?.block.name.trim() || void 0;
|
|
2376
|
+
const completedName = typeof value === "string" ? value.trim() || void 0 : void 0;
|
|
2377
|
+
if (streamedName && completedName && streamedName !== completedName) throw new Error(`Responses stream changed tool-call function name from ${streamedName} to ${completedName}`);
|
|
2378
|
+
const name = completedName ?? streamedName;
|
|
2379
|
+
if (!name) throw new Error("Responses stream completed tool call without a function name");
|
|
2380
|
+
return name;
|
|
2381
|
+
}
|
|
2382
|
+
function createResponsesTerminalController(params) {
|
|
2383
|
+
const { output, stream, model, options } = params;
|
|
2384
|
+
const blocks = output.content;
|
|
2385
|
+
const backfillReasoning = (items) => {
|
|
2386
|
+
for (const item of items) {
|
|
2387
|
+
if (item.type !== "reasoning" || !item.encrypted_content) continue;
|
|
2388
|
+
const block = params.reasoningBlocksById.get(item.id);
|
|
2389
|
+
if (!block?.thinkingSignature) continue;
|
|
2390
|
+
const stored = JSON.parse(block.thinkingSignature);
|
|
2391
|
+
if (!stored.encrypted_content) block.thinkingSignature = JSON.stringify({
|
|
2392
|
+
...stored,
|
|
2393
|
+
encrypted_content: item.encrypted_content
|
|
2394
|
+
});
|
|
2395
|
+
if (options?.reasoningReplayMetadata) block[OPENAI_RESPONSES_REASONING_REPLAY_BLOCK_META_KEY] = options.reasoningReplayMetadata;
|
|
2396
|
+
}
|
|
2397
|
+
};
|
|
2398
|
+
const appendText = (item) => {
|
|
2399
|
+
const text = (Array.isArray(item.content) ? item.content : []).map((part) => {
|
|
2400
|
+
const content = part;
|
|
2401
|
+
return content.type === "output_text" || content.type === "text" ? content.text ?? "" : content.refusal ?? "";
|
|
2402
|
+
}).join("");
|
|
2403
|
+
if (!text) return;
|
|
2404
|
+
const phase = item.phase ?? void 0;
|
|
2405
|
+
const previous = params.getLastTextBlock();
|
|
2406
|
+
const collapse = resolveResponsesMessageSnapshotCollapse({
|
|
2407
|
+
prior: previous && {
|
|
2408
|
+
text: previous.block.text,
|
|
2409
|
+
phase: previous.phase
|
|
2410
|
+
},
|
|
2411
|
+
nextText: text,
|
|
2412
|
+
nextPhase: phase
|
|
2413
|
+
});
|
|
2414
|
+
if (collapse.kind === "extend" && previous) {
|
|
2415
|
+
previous.block.text = collapse.text;
|
|
2416
|
+
previous.block.textSignature = encodeTextSignatureV1(item.id, phase);
|
|
2417
|
+
stream.push({
|
|
2418
|
+
type: "text_end",
|
|
2419
|
+
contentIndex: previous.index,
|
|
2420
|
+
content: collapse.text,
|
|
2421
|
+
partial: output
|
|
2422
|
+
});
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2425
|
+
const block = {
|
|
2426
|
+
type: "text",
|
|
2427
|
+
text,
|
|
2428
|
+
textSignature: encodeTextSignatureV1(item.id, phase)
|
|
2429
|
+
};
|
|
2430
|
+
blocks.push(block);
|
|
2431
|
+
const index = blocks.length - 1;
|
|
2432
|
+
params.setLastTextBlock({
|
|
2433
|
+
block,
|
|
2434
|
+
index,
|
|
2435
|
+
phase
|
|
2436
|
+
});
|
|
1857
2437
|
stream.push({
|
|
1858
|
-
type: "
|
|
2438
|
+
type: "text_start",
|
|
2439
|
+
contentIndex: index,
|
|
1859
2440
|
partial: output
|
|
1860
2441
|
});
|
|
1861
|
-
const firstEventTimeoutMs = getFirstStreamEventTimeoutMs(options);
|
|
1862
|
-
const onFirstEventTimeout = getFirstStreamEventTimeoutHandler(options);
|
|
1863
|
-
await processResponsesStream(openaiStream, output, stream, model, params.processStreamOptions || firstEventTimeoutMs !== void 0 || onFirstEventTimeout !== void 0 ? {
|
|
1864
|
-
...params.processStreamOptions,
|
|
1865
|
-
firstEventTimeoutMs: params.processStreamOptions?.firstEventTimeoutMs ?? firstEventTimeoutMs,
|
|
1866
|
-
abortFirstEventStream: params.processStreamOptions?.abortFirstEventStream ?? firstEventAbort.abort,
|
|
1867
|
-
onFirstEventTimeout: params.processStreamOptions?.onFirstEventTimeout ?? onFirstEventTimeout
|
|
1868
|
-
} : void 0);
|
|
1869
|
-
if (options?.signal?.aborted) throw new Error("Request was aborted");
|
|
1870
|
-
if (output.stopReason === "aborted" || output.stopReason === "error") throw new Error(output.errorMessage ?? "An unknown error occurred");
|
|
1871
2442
|
stream.push({
|
|
1872
|
-
type: "
|
|
1873
|
-
|
|
1874
|
-
|
|
2443
|
+
type: "text_end",
|
|
2444
|
+
contentIndex: index,
|
|
2445
|
+
content: text,
|
|
2446
|
+
partial: output
|
|
1875
2447
|
});
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
2448
|
+
};
|
|
2449
|
+
const appendToolCall = (item) => {
|
|
2450
|
+
const toolCall = {
|
|
2451
|
+
type: "toolCall",
|
|
2452
|
+
id: resolveResponsesToolCallId(item),
|
|
2453
|
+
name: resolveCompletedToolCallName(void 0, item.name),
|
|
2454
|
+
arguments: parseStreamingJson(item.arguments || "{}")
|
|
2455
|
+
};
|
|
2456
|
+
blocks.push(toolCall);
|
|
2457
|
+
const contentIndex = blocks.length - 1;
|
|
1881
2458
|
stream.push({
|
|
1882
|
-
type: "
|
|
1883
|
-
|
|
1884
|
-
|
|
2459
|
+
type: "toolcall_start",
|
|
2460
|
+
contentIndex,
|
|
2461
|
+
partial: output
|
|
1885
2462
|
});
|
|
1886
|
-
stream.
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
2463
|
+
stream.push({
|
|
2464
|
+
type: "toolcall_end",
|
|
2465
|
+
contentIndex,
|
|
2466
|
+
toolCall,
|
|
2467
|
+
partial: output
|
|
2468
|
+
});
|
|
2469
|
+
};
|
|
2470
|
+
const recoverTerminalOutput = (items, includeToolCalls) => {
|
|
2471
|
+
if (blocks.some((block) => block.type !== "thinking")) return;
|
|
2472
|
+
for (const item of items) if (item.type === "message") appendText(item);
|
|
2473
|
+
else {
|
|
2474
|
+
params.setLastTextBlock(null);
|
|
2475
|
+
if (includeToolCalls && item.type === "function_call") appendToolCall(item);
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
2478
|
+
const finalizeResponse = (response, terminalEventType) => {
|
|
2479
|
+
params.markFinalized();
|
|
2480
|
+
backfillReasoning(response.output ?? []);
|
|
2481
|
+
output.responseId = response.id || output.responseId;
|
|
2482
|
+
const usage = mapResponsesTerminalUsage(response.usage);
|
|
2483
|
+
const reasoningTokens = readResponsesReasoningTokens(response.usage);
|
|
2484
|
+
if (usage) output.usage = {
|
|
2485
|
+
...usage,
|
|
2486
|
+
...reasoningTokens === void 0 ? {} : { reasoningTokens },
|
|
2487
|
+
cost: {
|
|
2488
|
+
input: 0,
|
|
2489
|
+
output: 0,
|
|
2490
|
+
cacheRead: 0,
|
|
2491
|
+
cacheWrite: 0,
|
|
2492
|
+
total: 0
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
calculateCost(model, output.usage);
|
|
2496
|
+
if (options?.applyServiceTierPricing) {
|
|
2497
|
+
const tier = options.resolveServiceTier ? options.resolveServiceTier(response.service_tier, options.serviceTier) : response.service_tier ?? options.serviceTier;
|
|
2498
|
+
options.applyServiceTierPricing(output.usage, tier);
|
|
2499
|
+
}
|
|
2500
|
+
const terminal = resolveResponsesTerminalStopReason({
|
|
2501
|
+
status: response.status,
|
|
2502
|
+
terminalEventType,
|
|
2503
|
+
incompleteReason: response.incomplete_details?.reason,
|
|
2504
|
+
hasToolCall: blocks.some((block) => block.type === "toolCall")
|
|
2505
|
+
});
|
|
2506
|
+
output.stopReason = terminal.stopReason;
|
|
2507
|
+
output.errorMessage = terminal.errorMessage;
|
|
2508
|
+
};
|
|
2509
|
+
return {
|
|
2510
|
+
finalizeResponse,
|
|
2511
|
+
recoverTerminalOutput
|
|
2512
|
+
};
|
|
1890
2513
|
}
|
|
2514
|
+
//#endregion
|
|
2515
|
+
//#region packages/ai/src/transports/openai-responses-stream-internal.ts
|
|
2516
|
+
var ResponsesStreamFailure = class extends Error {
|
|
2517
|
+
constructor(failure, response) {
|
|
2518
|
+
super(failure.message);
|
|
2519
|
+
this.name = "ResponsesStreamFailure";
|
|
2520
|
+
this.responseId = failure.responseId;
|
|
2521
|
+
this.response = response;
|
|
2522
|
+
this.observation = failure.observation;
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
1891
2525
|
async function processResponsesStream(openaiStream, output, stream, model, options) {
|
|
1892
2526
|
const streamingToolCalls = createResponsesToolCallTracker();
|
|
1893
|
-
const outputSlots =
|
|
2527
|
+
const outputSlots = createResponsesOutputSlotTracker();
|
|
1894
2528
|
const reasoningBlocksById = /* @__PURE__ */ new Map();
|
|
1895
|
-
let unindexedOutputSlot;
|
|
1896
2529
|
let terminalResponseEvent;
|
|
1897
2530
|
let lastTextBlock = null;
|
|
1898
2531
|
const blocks = output.content;
|
|
1899
2532
|
const blockIndex = () => blocks.length - 1;
|
|
1900
|
-
const readOutputIndex = (event) => {
|
|
1901
|
-
const outputIndex = event.output_index;
|
|
1902
|
-
return typeof outputIndex === "number" && Number.isInteger(outputIndex) && outputIndex >= 0 ? outputIndex : void 0;
|
|
1903
|
-
};
|
|
1904
|
-
const registerOutputSlot = (event, slot) => {
|
|
1905
|
-
const outputIndex = readOutputIndex(event);
|
|
1906
|
-
if (outputIndex === void 0) {
|
|
1907
|
-
if (unindexedOutputSlot) throw new Error("Responses stream added overlapping unindexed output items");
|
|
1908
|
-
unindexedOutputSlot = slot;
|
|
1909
|
-
return;
|
|
1910
|
-
}
|
|
1911
|
-
if (outputSlots.has(outputIndex)) throw new Error(`Responses stream reused active output index ${outputIndex}`);
|
|
1912
|
-
outputSlots.set(outputIndex, slot);
|
|
1913
|
-
};
|
|
1914
|
-
const resolveOutputSlot = (event, type) => {
|
|
1915
|
-
const outputIndex = readOutputIndex(event);
|
|
1916
|
-
let slot = outputIndex === void 0 ? unindexedOutputSlot : outputSlots.get(outputIndex);
|
|
1917
|
-
if (outputIndex === void 0 && !slot) {
|
|
1918
|
-
const matchingSlots = [...outputSlots.values()].filter((candidate) => candidate.type === type);
|
|
1919
|
-
slot = matchingSlots.length === 1 ? matchingSlots[0] : void 0;
|
|
1920
|
-
}
|
|
1921
|
-
return slot?.type === type ? slot : void 0;
|
|
1922
|
-
};
|
|
1923
|
-
const forgetOutputSlot = (event, slot) => {
|
|
1924
|
-
const outputIndex = readOutputIndex(event);
|
|
1925
|
-
if (outputIndex === void 0) {
|
|
1926
|
-
if (unindexedOutputSlot === slot) unindexedOutputSlot = void 0;
|
|
1927
|
-
else for (const [indexedOutput, indexedSlot] of outputSlots) if (indexedSlot === slot) outputSlots.delete(indexedOutput);
|
|
1928
|
-
return;
|
|
1929
|
-
}
|
|
1930
|
-
if (outputSlots.get(outputIndex) === slot) outputSlots.delete(outputIndex);
|
|
1931
|
-
};
|
|
1932
|
-
const forgetToolCallOutputSlot = (toolCall) => {
|
|
1933
|
-
for (const [outputIndex, slot] of outputSlots) if (slot.type === "toolCall" && slot.toolCall === toolCall) outputSlots.delete(outputIndex);
|
|
1934
|
-
};
|
|
1935
|
-
const readIdentityValue = (value) => {
|
|
1936
|
-
return (typeof value === "string" ? value.trim() : "") || void 0;
|
|
1937
|
-
};
|
|
1938
|
-
const resolveCompletedToolCallName = (toolCall, value) => {
|
|
1939
|
-
const streamedName = readIdentityValue(toolCall?.block.name);
|
|
1940
|
-
const completedName = readIdentityValue(value);
|
|
1941
|
-
if (streamedName && completedName && streamedName !== completedName) throw new Error(`Responses stream changed tool-call function name from ${streamedName} to ${completedName}`);
|
|
1942
|
-
const name = completedName ?? streamedName;
|
|
1943
|
-
if (!name) throw new Error("Responses stream completed tool call without a function name");
|
|
1944
|
-
return name;
|
|
1945
|
-
};
|
|
1946
2533
|
const createOutputSlot = (event, item) => {
|
|
1947
2534
|
if (item.type === "reasoning") {
|
|
1948
2535
|
const block = {
|
|
@@ -1956,7 +2543,7 @@ async function processResponsesStream(openaiStream, output, stream, model, optio
|
|
|
1956
2543
|
contentIndex: blocks.length
|
|
1957
2544
|
};
|
|
1958
2545
|
blocks.push(block);
|
|
1959
|
-
|
|
2546
|
+
outputSlots.register(event, slot);
|
|
1960
2547
|
stream.push({
|
|
1961
2548
|
type: "thinking_start",
|
|
1962
2549
|
contentIndex: slot.contentIndex,
|
|
@@ -1981,7 +2568,7 @@ async function processResponsesStream(openaiStream, output, stream, model, optio
|
|
|
1981
2568
|
collapseCandidate
|
|
1982
2569
|
};
|
|
1983
2570
|
if (block) blocks.push(block);
|
|
1984
|
-
|
|
2571
|
+
outputSlots.register(event, slot);
|
|
1985
2572
|
if (slot.contentIndex !== void 0) stream.push({
|
|
1986
2573
|
type: "text_start",
|
|
1987
2574
|
contentIndex: slot.contentIndex,
|
|
@@ -1991,10 +2578,9 @@ async function processResponsesStream(openaiStream, output, stream, model, optio
|
|
|
1991
2578
|
}
|
|
1992
2579
|
};
|
|
1993
2580
|
const resolveOutputItemSlot = (event, item) => {
|
|
1994
|
-
if (item.type === "reasoning") return
|
|
1995
|
-
if (item.type === "message") return
|
|
1996
|
-
|
|
1997
|
-
return outputIndex === void 0 ? void 0 : outputSlots.get(outputIndex);
|
|
2581
|
+
if (item.type === "reasoning") return outputSlots.resolve(event, "thinking");
|
|
2582
|
+
if (item.type === "message") return outputSlots.resolve(event, "text");
|
|
2583
|
+
return readResponsesOutputIndex(event) === void 0 ? void 0 : outputSlots.get(event);
|
|
1998
2584
|
};
|
|
1999
2585
|
const getOrCreateOutputSlot = (event, item) => {
|
|
2000
2586
|
return resolveOutputItemSlot(event, item) ?? createOutputSlot(event, item);
|
|
@@ -2017,8 +2603,7 @@ async function processResponsesStream(openaiStream, output, stream, model, optio
|
|
|
2017
2603
|
if (text) stream.push({
|
|
2018
2604
|
type: "text_delta",
|
|
2019
2605
|
contentIndex: slot.contentIndex,
|
|
2020
|
-
delta: text
|
|
2021
|
-
partial: output
|
|
2606
|
+
delta: text
|
|
2022
2607
|
});
|
|
2023
2608
|
if (lastTextBlock === slot.collapseCandidate) lastTextBlock = null;
|
|
2024
2609
|
slot.pendingText = null;
|
|
@@ -2026,7 +2611,6 @@ async function processResponsesStream(openaiStream, output, stream, model, optio
|
|
|
2026
2611
|
};
|
|
2027
2612
|
const materializeDeferredTextSlots = (except) => {
|
|
2028
2613
|
for (const slot of outputSlots.values()) if (slot !== except && slot.type === "text") materializeDeferredTextSlot(slot);
|
|
2029
|
-
if (unindexedOutputSlot !== except && unindexedOutputSlot?.type === "text") materializeDeferredTextSlot(unindexedOutputSlot);
|
|
2030
2614
|
};
|
|
2031
2615
|
const appendPendingMessageDelta = (slot, delta) => {
|
|
2032
2616
|
slot.pendingText = `${slot.pendingText ?? ""}${delta}`;
|
|
@@ -2034,48 +2618,21 @@ async function processResponsesStream(openaiStream, output, stream, model, optio
|
|
|
2034
2618
|
if (priorText.startsWith(slot.pendingText) || slot.pendingText.startsWith(priorText)) return;
|
|
2035
2619
|
materializeDeferredTextSlot(slot);
|
|
2036
2620
|
};
|
|
2037
|
-
const
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
};
|
|
2050
|
-
const finalizeResponse = (response) => {
|
|
2051
|
-
terminalResponseEvent = "finalized";
|
|
2052
|
-
backfillReasoningSignatures(response.output ?? []);
|
|
2053
|
-
if (response.id) output.responseId = response.id;
|
|
2054
|
-
const mappedUsage = mapResponsesTerminalUsage(response.usage);
|
|
2055
|
-
if (mappedUsage) output.usage = {
|
|
2056
|
-
...mappedUsage,
|
|
2057
|
-
cost: {
|
|
2058
|
-
input: 0,
|
|
2059
|
-
output: 0,
|
|
2060
|
-
cacheRead: 0,
|
|
2061
|
-
cacheWrite: 0,
|
|
2062
|
-
total: 0
|
|
2063
|
-
}
|
|
2064
|
-
};
|
|
2065
|
-
calculateCost(model, output.usage);
|
|
2066
|
-
if (options?.applyServiceTierPricing) {
|
|
2067
|
-
const serviceTier = options.resolveServiceTier ? options.resolveServiceTier(response.service_tier, options.serviceTier) : response.service_tier ?? options.serviceTier;
|
|
2068
|
-
options.applyServiceTierPricing(output.usage, serviceTier);
|
|
2621
|
+
const { finalizeResponse, recoverTerminalOutput } = createResponsesTerminalController({
|
|
2622
|
+
output,
|
|
2623
|
+
stream,
|
|
2624
|
+
model,
|
|
2625
|
+
options,
|
|
2626
|
+
reasoningBlocksById,
|
|
2627
|
+
getLastTextBlock: () => lastTextBlock,
|
|
2628
|
+
setLastTextBlock: (block) => {
|
|
2629
|
+
lastTextBlock = block;
|
|
2630
|
+
},
|
|
2631
|
+
markFinalized: () => {
|
|
2632
|
+
terminalResponseEvent = "finalized";
|
|
2069
2633
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
incompleteReason: response.incomplete_details?.reason,
|
|
2073
|
-
hasToolCall: output.content.some((block) => block.type === "toolCall")
|
|
2074
|
-
});
|
|
2075
|
-
output.stopReason = terminal.stopReason;
|
|
2076
|
-
if (terminal.errorMessage) output.errorMessage = terminal.errorMessage;
|
|
2077
|
-
};
|
|
2078
|
-
const guardedStream = withFirstStreamEventTimeout(openaiStream, {
|
|
2634
|
+
});
|
|
2635
|
+
const guardedStream = adaptResponsesStream(withFirstStreamEventTimeout(openaiStream, {
|
|
2079
2636
|
provider: model.provider,
|
|
2080
2637
|
api: model.api,
|
|
2081
2638
|
model: model.id,
|
|
@@ -2084,309 +2641,324 @@ async function processResponsesStream(openaiStream, output, stream, model, optio
|
|
|
2084
2641
|
abort: options?.abortFirstEventStream,
|
|
2085
2642
|
onTimeout: options?.onFirstEventTimeout,
|
|
2086
2643
|
hint: "The provider may be stalled while parsing the tool payload; retry with a smaller tool surface or enable OPENCLAW_DEBUG_MODEL_PAYLOAD=tools to inspect exposed tools."
|
|
2087
|
-
});
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
type: "thinking_delta",
|
|
2136
|
-
contentIndex: slot.contentIndex,
|
|
2137
|
-
delta: event.delta,
|
|
2138
|
-
partial: output
|
|
2139
|
-
});
|
|
2140
|
-
} else if (event.type === "response.reasoning_summary_part.done") {
|
|
2141
|
-
const slot = resolveOutputSlot(event, "thinking");
|
|
2142
|
-
if (!slot) continue;
|
|
2143
|
-
slot.item.summary = slot.item.summary || [];
|
|
2144
|
-
const lastPart = slot.item.summary[slot.item.summary.length - 1];
|
|
2145
|
-
if (!lastPart) continue;
|
|
2146
|
-
slot.block.thinking += "\n\n";
|
|
2147
|
-
lastPart.text += "\n\n";
|
|
2148
|
-
stream.push({
|
|
2149
|
-
type: "thinking_delta",
|
|
2150
|
-
contentIndex: slot.contentIndex,
|
|
2151
|
-
delta: "\n\n",
|
|
2152
|
-
partial: output
|
|
2153
|
-
});
|
|
2154
|
-
} else if (event.type === "response.reasoning_text.delta") {
|
|
2155
|
-
const slot = resolveOutputSlot(event, "thinking");
|
|
2156
|
-
if (!slot) continue;
|
|
2157
|
-
slot.block.thinking += event.delta;
|
|
2158
|
-
stream.push({
|
|
2159
|
-
type: "thinking_delta",
|
|
2160
|
-
contentIndex: slot.contentIndex,
|
|
2161
|
-
delta: event.delta,
|
|
2162
|
-
partial: output
|
|
2163
|
-
});
|
|
2164
|
-
} else if (event.type === "response.content_part.added") {
|
|
2165
|
-
const slot = resolveOutputSlot(event, "text");
|
|
2166
|
-
if (!slot) continue;
|
|
2167
|
-
slot.item.content = slot.item.content || [];
|
|
2168
|
-
if (event.part.type === "output_text" || event.part.type === "text" || event.part.type === "refusal") slot.item.content.push(event.part);
|
|
2169
|
-
} else if (event.type === "response.output_text.delta") {
|
|
2170
|
-
const slot = resolveOutputSlot(event, "text");
|
|
2171
|
-
if (!slot?.item.content || slot.item.content.length === 0) continue;
|
|
2172
|
-
const lastPart = slot.item.content[slot.item.content.length - 1];
|
|
2173
|
-
if (!isResponsesTextContentPartType(lastPart?.type)) continue;
|
|
2174
|
-
lastPart.text += event.delta;
|
|
2175
|
-
if (slot.pendingText !== null) appendPendingMessageDelta(slot, event.delta);
|
|
2176
|
-
else if (slot.block && slot.contentIndex !== void 0) {
|
|
2177
|
-
slot.block.text += event.delta;
|
|
2644
|
+
}), options?.signal);
|
|
2645
|
+
try {
|
|
2646
|
+
for await (const event of guardedStream) if (event.type === "response.created") output.responseId = event.response.id;
|
|
2647
|
+
else if (event.type === "response.output_item.added") {
|
|
2648
|
+
materializeDeferredTextSlots();
|
|
2649
|
+
const item = event.item;
|
|
2650
|
+
if (item.type !== "message") lastTextBlock = null;
|
|
2651
|
+
if (item.type === "reasoning" || item.type === "message") createOutputSlot(event, item);
|
|
2652
|
+
else if (item.type === "function_call") {
|
|
2653
|
+
const toolCallBlock = {
|
|
2654
|
+
type: "toolCall",
|
|
2655
|
+
id: resolveResponsesToolCallId(item),
|
|
2656
|
+
name: typeof item.name === "string" ? item.name.trim() : "",
|
|
2657
|
+
arguments: {},
|
|
2658
|
+
partialJson: item.arguments || ""
|
|
2659
|
+
};
|
|
2660
|
+
const contentIndex = output.content.length;
|
|
2661
|
+
const toolCallState = {
|
|
2662
|
+
block: toolCallBlock,
|
|
2663
|
+
contentIndex,
|
|
2664
|
+
argumentStreamReliable: true,
|
|
2665
|
+
...readResponsesToolCallItemIdentity(item)
|
|
2666
|
+
};
|
|
2667
|
+
streamingToolCalls.register(event, toolCallState);
|
|
2668
|
+
if (readResponsesOutputIndex(event) !== void 0) outputSlots.register(event, {
|
|
2669
|
+
type: "toolCall",
|
|
2670
|
+
toolCall: toolCallState
|
|
2671
|
+
});
|
|
2672
|
+
output.content.push(toolCallBlock);
|
|
2673
|
+
stream.push({
|
|
2674
|
+
type: "toolcall_start",
|
|
2675
|
+
contentIndex,
|
|
2676
|
+
partial: output
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
} else if (event.type === "response.reasoning_summary_part.added") {
|
|
2680
|
+
const slot = outputSlots.resolve(event, "thinking");
|
|
2681
|
+
if (!slot) continue;
|
|
2682
|
+
slot.item.summary = slot.item.summary || [];
|
|
2683
|
+
slot.item.summary.push(event.part);
|
|
2684
|
+
} else if (event.type === "response.reasoning_summary_text.delta") {
|
|
2685
|
+
const slot = outputSlots.resolve(event, "thinking");
|
|
2686
|
+
if (!slot) continue;
|
|
2687
|
+
slot.item.summary = slot.item.summary || [];
|
|
2688
|
+
const lastPart = slot.item.summary[slot.item.summary.length - 1];
|
|
2689
|
+
if (!lastPart) continue;
|
|
2690
|
+
slot.block.thinking += event.delta;
|
|
2691
|
+
lastPart.text += event.delta;
|
|
2178
2692
|
stream.push({
|
|
2179
|
-
type: "
|
|
2693
|
+
type: "thinking_delta",
|
|
2180
2694
|
contentIndex: slot.contentIndex,
|
|
2181
2695
|
delta: event.delta,
|
|
2182
2696
|
partial: output
|
|
2183
2697
|
});
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
lastPart
|
|
2192
|
-
type: "text",
|
|
2193
|
-
text: ""
|
|
2194
|
-
};
|
|
2195
|
-
slot.item.content.push(lastPart);
|
|
2196
|
-
}
|
|
2197
|
-
lastPart.text += event.delta;
|
|
2198
|
-
if (slot.pendingText !== null) appendPendingMessageDelta(slot, event.delta);
|
|
2199
|
-
else if (slot.block && slot.contentIndex !== void 0) {
|
|
2200
|
-
slot.block.text += event.delta;
|
|
2698
|
+
} else if (event.type === "response.reasoning_summary_part.done") {
|
|
2699
|
+
const slot = outputSlots.resolve(event, "thinking");
|
|
2700
|
+
if (!slot) continue;
|
|
2701
|
+
slot.item.summary = slot.item.summary || [];
|
|
2702
|
+
const lastPart = slot.item.summary[slot.item.summary.length - 1];
|
|
2703
|
+
if (!lastPart) continue;
|
|
2704
|
+
slot.block.thinking += "\n\n";
|
|
2705
|
+
lastPart.text += "\n\n";
|
|
2201
2706
|
stream.push({
|
|
2202
|
-
type: "
|
|
2707
|
+
type: "thinking_delta",
|
|
2203
2708
|
contentIndex: slot.contentIndex,
|
|
2204
|
-
delta:
|
|
2709
|
+
delta: "\n\n",
|
|
2205
2710
|
partial: output
|
|
2206
2711
|
});
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
const lastPart = slot.item.content[slot.item.content.length - 1];
|
|
2212
|
-
if (lastPart?.type !== "refusal") continue;
|
|
2213
|
-
lastPart.refusal += event.delta;
|
|
2214
|
-
if (slot.pendingText !== null) appendPendingMessageDelta(slot, event.delta);
|
|
2215
|
-
else if (slot.block && slot.contentIndex !== void 0) {
|
|
2216
|
-
slot.block.text += event.delta;
|
|
2712
|
+
} else if (event.type === "response.reasoning_text.delta") {
|
|
2713
|
+
const slot = outputSlots.resolve(event, "thinking");
|
|
2714
|
+
if (!slot) continue;
|
|
2715
|
+
slot.block.thinking += event.delta;
|
|
2217
2716
|
stream.push({
|
|
2218
|
-
type: "
|
|
2717
|
+
type: "thinking_delta",
|
|
2219
2718
|
contentIndex: slot.contentIndex,
|
|
2220
2719
|
delta: event.delta,
|
|
2221
2720
|
partial: output
|
|
2222
2721
|
});
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
const doneArguments = typeof event.arguments === "string" ? event.arguments : void 0;
|
|
2241
|
-
if (doneArguments !== void 0 && (doneArguments.length > 0 || previousPartialJson === "")) {
|
|
2242
|
-
toolCall.block.partialJson = doneArguments;
|
|
2243
|
-
toolCall.block.arguments = parseStreamingJson(toolCall.block.partialJson);
|
|
2244
|
-
toolCall.argumentStreamReliable = true;
|
|
2722
|
+
} else if (event.type === "response.content_part.added") {
|
|
2723
|
+
const slot = outputSlots.resolve(event, "text");
|
|
2724
|
+
if (!slot) continue;
|
|
2725
|
+
slot.item.content = slot.item.content || [];
|
|
2726
|
+
if (event.part.type === "output_text" || event.part.type === "text" || event.part.type === "refusal") slot.item.content.push(event.part);
|
|
2727
|
+
} else if (event.type === "response.output_text.delta") {
|
|
2728
|
+
const slot = outputSlots.resolve(event, "text");
|
|
2729
|
+
if (!slot) continue;
|
|
2730
|
+
slot.item.content ||= [];
|
|
2731
|
+
let lastPart = slot.item.content[slot.item.content.length - 1];
|
|
2732
|
+
if (!isResponsesTextContentPartType(lastPart?.type)) {
|
|
2733
|
+
lastPart = {
|
|
2734
|
+
type: "output_text",
|
|
2735
|
+
text: "",
|
|
2736
|
+
annotations: []
|
|
2737
|
+
};
|
|
2738
|
+
slot.item.content.push(lastPart);
|
|
2245
2739
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2740
|
+
lastPart.text += event.delta;
|
|
2741
|
+
if (slot.pendingText !== null) appendPendingMessageDelta(slot, event.delta);
|
|
2742
|
+
else if (slot.block && slot.contentIndex !== void 0) {
|
|
2743
|
+
slot.block.text += event.delta;
|
|
2744
|
+
stream.push({
|
|
2745
|
+
type: "text_delta",
|
|
2746
|
+
contentIndex: slot.contentIndex,
|
|
2747
|
+
delta: event.delta
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
} else if (isAzureResponsesTextDeltaEvent(event)) {
|
|
2751
|
+
const slot = outputSlots.resolve(event, "text");
|
|
2752
|
+
if (!slot) continue;
|
|
2753
|
+
slot.item.content = slot.item.content || [];
|
|
2754
|
+
let lastPart = slot.item.content[slot.item.content.length - 1];
|
|
2755
|
+
if (lastPart?.type !== "text") {
|
|
2756
|
+
lastPart = {
|
|
2757
|
+
type: "text",
|
|
2758
|
+
text: ""
|
|
2759
|
+
};
|
|
2760
|
+
slot.item.content.push(lastPart);
|
|
2761
|
+
}
|
|
2762
|
+
lastPart.text += event.delta;
|
|
2763
|
+
if (slot.pendingText !== null) appendPendingMessageDelta(slot, event.delta);
|
|
2764
|
+
else if (slot.block && slot.contentIndex !== void 0) {
|
|
2765
|
+
slot.block.text += event.delta;
|
|
2766
|
+
stream.push({
|
|
2767
|
+
type: "text_delta",
|
|
2768
|
+
contentIndex: slot.contentIndex,
|
|
2769
|
+
delta: event.delta
|
|
2770
|
+
});
|
|
2771
|
+
}
|
|
2772
|
+
} else if (event.type === "response.refusal.delta") {
|
|
2773
|
+
const slot = outputSlots.resolve(event, "text");
|
|
2774
|
+
if (!slot) continue;
|
|
2775
|
+
slot.item.content ||= [];
|
|
2776
|
+
let lastPart = slot.item.content[slot.item.content.length - 1];
|
|
2777
|
+
if (lastPart?.type !== "refusal") {
|
|
2778
|
+
lastPart = {
|
|
2779
|
+
type: "refusal",
|
|
2780
|
+
refusal: ""
|
|
2781
|
+
};
|
|
2782
|
+
slot.item.content.push(lastPart);
|
|
2783
|
+
}
|
|
2784
|
+
lastPart.refusal += event.delta;
|
|
2785
|
+
if (slot.pendingText !== null) appendPendingMessageDelta(slot, event.delta);
|
|
2786
|
+
else if (slot.block && slot.contentIndex !== void 0) {
|
|
2787
|
+
slot.block.text += event.delta;
|
|
2788
|
+
stream.push({
|
|
2789
|
+
type: "text_delta",
|
|
2790
|
+
contentIndex: slot.contentIndex,
|
|
2791
|
+
delta: event.delta
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
} else if (event.type === "response.function_call_arguments.delta") {
|
|
2795
|
+
const toolCall = streamingToolCalls.resolve(event);
|
|
2796
|
+
if (toolCall) {
|
|
2797
|
+
toolCall.block.partialJson += event.delta;
|
|
2798
|
+
toolCall.block.arguments = parseStreamingJson(toolCall.block.partialJson);
|
|
2799
|
+
stream.push({
|
|
2249
2800
|
type: "toolcall_delta",
|
|
2250
2801
|
contentIndex: toolCall.contentIndex,
|
|
2251
|
-
delta,
|
|
2802
|
+
delta: event.delta,
|
|
2252
2803
|
partial: output
|
|
2253
2804
|
});
|
|
2254
|
-
}
|
|
2255
|
-
} else if (
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
} else if (
|
|
2276
|
-
const
|
|
2277
|
-
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
if (collapse.kind === "extend" && outputSlot.collapseCandidate) {
|
|
2289
|
-
outputSlot.collapseCandidate.block.text = collapse.text;
|
|
2290
|
-
outputSlot.collapseCandidate.block.textSignature = encodeTextSignatureV1(item.id, phase);
|
|
2805
|
+
} else if (streamingToolCalls.hasActive()) streamingToolCalls.markArgumentsUnreliable();
|
|
2806
|
+
} else if (event.type === "response.function_call_arguments.done") {
|
|
2807
|
+
const toolCall = streamingToolCalls.resolve(event);
|
|
2808
|
+
if (toolCall) {
|
|
2809
|
+
const previousPartialJson = toolCall.block.partialJson;
|
|
2810
|
+
const doneArguments = typeof event.arguments === "string" ? event.arguments : void 0;
|
|
2811
|
+
if (doneArguments !== void 0 && (doneArguments.length > 0 || previousPartialJson === "")) {
|
|
2812
|
+
toolCall.block.partialJson = doneArguments;
|
|
2813
|
+
toolCall.block.arguments = parseStreamingJson(toolCall.block.partialJson);
|
|
2814
|
+
toolCall.argumentStreamReliable = true;
|
|
2815
|
+
}
|
|
2816
|
+
if (doneArguments?.startsWith(previousPartialJson)) {
|
|
2817
|
+
const delta = doneArguments.slice(previousPartialJson.length);
|
|
2818
|
+
if (delta.length > 0) stream.push({
|
|
2819
|
+
type: "toolcall_delta",
|
|
2820
|
+
contentIndex: toolCall.contentIndex,
|
|
2821
|
+
delta,
|
|
2822
|
+
partial: output
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
} else if (streamingToolCalls.hasActive()) streamingToolCalls.markArgumentsUnreliable();
|
|
2826
|
+
} else if (event.type === "response.output_item.done") {
|
|
2827
|
+
const item = event.item;
|
|
2828
|
+
if (item.type !== "message") lastTextBlock = null;
|
|
2829
|
+
const existingOutputSlot = resolveOutputItemSlot(event, item);
|
|
2830
|
+
materializeDeferredTextSlots(existingOutputSlot);
|
|
2831
|
+
const outputSlot = existingOutputSlot ?? getOrCreateOutputSlot(event, item);
|
|
2832
|
+
if (item.type === "reasoning" && outputSlot?.type === "thinking") {
|
|
2833
|
+
const summaryText = item.summary?.map((s) => s.text).join("\n\n") || "";
|
|
2834
|
+
const contentText = item.content?.map((c) => c.text).join("\n\n") || "";
|
|
2835
|
+
outputSlot.block.thinking = summaryText || contentText || outputSlot.block.thinking;
|
|
2836
|
+
outputSlot.block.thinkingSignature = JSON.stringify(item);
|
|
2837
|
+
if (item.encrypted_content && options?.reasoningReplayMetadata) outputSlot.block[OPENAI_RESPONSES_REASONING_REPLAY_BLOCK_META_KEY] = options.reasoningReplayMetadata;
|
|
2838
|
+
if (typeof item.id === "string") reasoningBlocksById.set(item.id, outputSlot.block);
|
|
2291
2839
|
stream.push({
|
|
2292
|
-
type: "
|
|
2293
|
-
contentIndex: outputSlot.
|
|
2294
|
-
content:
|
|
2840
|
+
type: "thinking_end",
|
|
2841
|
+
contentIndex: outputSlot.contentIndex,
|
|
2842
|
+
content: outputSlot.block.thinking,
|
|
2295
2843
|
partial: output
|
|
2296
2844
|
});
|
|
2297
|
-
|
|
2298
|
-
} else {
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2845
|
+
outputSlots.forget(outputSlot);
|
|
2846
|
+
} else if (item.type === "message" && outputSlot?.type === "text" && (outputSlot.block || outputSlot.pendingText !== null)) {
|
|
2847
|
+
const streamedText = outputSlot.pendingText ?? outputSlot.block?.text ?? "";
|
|
2848
|
+
const finalText = item.content == null ? streamedText : item.content.map((c) => c.type === "output_text" || c.type === "text" ? c.text : c.refusal).join("");
|
|
2849
|
+
const phase = item.phase ?? void 0;
|
|
2850
|
+
const collapse = outputSlot.pendingText !== null ? resolveResponsesMessageSnapshotCollapse({
|
|
2851
|
+
prior: outputSlot.collapseCandidate && {
|
|
2852
|
+
text: outputSlot.collapseCandidate.block.text,
|
|
2853
|
+
phase: outputSlot.collapseCandidate.phase
|
|
2854
|
+
},
|
|
2855
|
+
nextText: finalText,
|
|
2856
|
+
nextPhase: phase
|
|
2857
|
+
}) : { kind: "keep" };
|
|
2858
|
+
outputSlot.pendingText = null;
|
|
2859
|
+
if (collapse.kind === "extend" && outputSlot.collapseCandidate) {
|
|
2860
|
+
outputSlot.collapseCandidate.block.text = collapse.text;
|
|
2861
|
+
outputSlot.collapseCandidate.block.textSignature = encodeTextSignatureV1(item.id, phase);
|
|
2862
|
+
stream.push({
|
|
2863
|
+
type: "text_end",
|
|
2864
|
+
contentIndex: outputSlot.collapseCandidate.index,
|
|
2865
|
+
content: collapse.text,
|
|
2866
|
+
partial: output
|
|
2867
|
+
});
|
|
2868
|
+
lastTextBlock = outputSlot.collapseCandidate;
|
|
2869
|
+
} else {
|
|
2870
|
+
if (!outputSlot.block) {
|
|
2871
|
+
outputSlot.block = {
|
|
2872
|
+
type: "text",
|
|
2873
|
+
text: "",
|
|
2874
|
+
...phase ? { textSignature: encodeTextSignatureV1(item.id, phase) } : {}
|
|
2875
|
+
};
|
|
2876
|
+
blocks.push(outputSlot.block);
|
|
2877
|
+
outputSlot.contentIndex = blockIndex();
|
|
2878
|
+
stream.push({
|
|
2879
|
+
type: "text_start",
|
|
2880
|
+
contentIndex: outputSlot.contentIndex,
|
|
2881
|
+
partial: output
|
|
2882
|
+
});
|
|
2883
|
+
}
|
|
2884
|
+
outputSlot.block.text = finalText;
|
|
2885
|
+
outputSlot.block.textSignature = encodeTextSignatureV1(item.id, phase);
|
|
2886
|
+
const contentIndex = outputSlot.contentIndex;
|
|
2887
|
+
if (contentIndex === void 0) throw new Error("Responses stream finalized text without a content index");
|
|
2888
|
+
lastTextBlock = {
|
|
2889
|
+
block: outputSlot.block,
|
|
2890
|
+
index: contentIndex,
|
|
2891
|
+
phase
|
|
2304
2892
|
};
|
|
2305
|
-
blocks.push(outputSlot.block);
|
|
2306
|
-
outputSlot.contentIndex = blockIndex();
|
|
2307
2893
|
stream.push({
|
|
2308
|
-
type: "
|
|
2309
|
-
contentIndex
|
|
2894
|
+
type: "text_end",
|
|
2895
|
+
contentIndex,
|
|
2896
|
+
content: outputSlot.block.text,
|
|
2310
2897
|
partial: output
|
|
2311
2898
|
});
|
|
2312
2899
|
}
|
|
2313
|
-
outputSlot
|
|
2314
|
-
|
|
2315
|
-
const
|
|
2316
|
-
if (
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
};
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
id: resolveResponsesToolCallId(item),
|
|
2352
|
-
name: completedName,
|
|
2353
|
-
arguments: args
|
|
2354
|
-
};
|
|
2355
|
-
blocks.push(toolCall);
|
|
2356
|
-
contentIndex = blockIndex();
|
|
2900
|
+
outputSlots.forget(outputSlot);
|
|
2901
|
+
} else if (item.type === "function_call") {
|
|
2902
|
+
const streamingToolCall = streamingToolCalls.resolve(event, readResponsesToolCallItemIdentity(item));
|
|
2903
|
+
if (!streamingToolCall && streamingToolCalls.hasActive()) continue;
|
|
2904
|
+
const completedName = resolveCompletedToolCallName(streamingToolCall, item.name);
|
|
2905
|
+
const streamedArguments = streamingToolCall?.block.partialJson ?? "";
|
|
2906
|
+
const completedArguments = typeof item.arguments === "string" ? item.arguments : void 0;
|
|
2907
|
+
if (streamingToolCall && !streamingToolCall.argumentStreamReliable && !completedArguments) continue;
|
|
2908
|
+
const args = parseStreamingJson(completedArguments !== void 0 && (completedArguments.length > 0 || !streamedArguments) ? completedArguments : streamedArguments || "{}");
|
|
2909
|
+
let toolCall;
|
|
2910
|
+
let contentIndex;
|
|
2911
|
+
if (streamingToolCall) {
|
|
2912
|
+
const block = streamingToolCall.block;
|
|
2913
|
+
block.id = resolveResponsesToolCallId(item, block.id);
|
|
2914
|
+
block.name = completedName;
|
|
2915
|
+
block.arguments = args;
|
|
2916
|
+
delete block.partialJson;
|
|
2917
|
+
toolCall = block;
|
|
2918
|
+
contentIndex = streamingToolCall.contentIndex;
|
|
2919
|
+
} else {
|
|
2920
|
+
toolCall = {
|
|
2921
|
+
type: "toolCall",
|
|
2922
|
+
id: resolveResponsesToolCallId(item),
|
|
2923
|
+
name: completedName,
|
|
2924
|
+
arguments: args
|
|
2925
|
+
};
|
|
2926
|
+
blocks.push(toolCall);
|
|
2927
|
+
contentIndex = blockIndex();
|
|
2928
|
+
stream.push({
|
|
2929
|
+
type: "toolcall_start",
|
|
2930
|
+
contentIndex,
|
|
2931
|
+
partial: output
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
if (streamingToolCall) {
|
|
2935
|
+
streamingToolCalls.forget(streamingToolCall);
|
|
2936
|
+
for (const slot of outputSlots.values()) if (slot.type === "toolCall" && slot.toolCall === streamingToolCall) outputSlots.forget(slot);
|
|
2937
|
+
}
|
|
2357
2938
|
stream.push({
|
|
2358
|
-
type: "
|
|
2939
|
+
type: "toolcall_end",
|
|
2359
2940
|
contentIndex,
|
|
2941
|
+
toolCall,
|
|
2360
2942
|
partial: output
|
|
2361
2943
|
});
|
|
2362
2944
|
}
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2945
|
+
} else if (event.type === "response.completed" || event.type === "response.incomplete") {
|
|
2946
|
+
if (streamingToolCalls.hasActive()) throw new Error("Responses stream completed with unresolved tool calls");
|
|
2947
|
+
finalizeResponse(event.response, event.type);
|
|
2948
|
+
if (event.type === "response.completed" || output.stopReason === "length") recoverTerminalOutput(event.response.output ?? [], event.type === "response.completed");
|
|
2949
|
+
if (output.stopReason === "stop" && output.content.some((block) => block.type === "toolCall")) output.stopReason = "toolUse";
|
|
2950
|
+
break;
|
|
2951
|
+
} else if (event.type === "error") throw new Error(event.message ? `Error Code ${event.code}: ${event.message}` : "Unknown error");
|
|
2952
|
+
else if (event.type === "response.failed") {
|
|
2953
|
+
const failure = normalizeResponsesFailedEvent(event, model);
|
|
2954
|
+
if (failure.responseId) output.responseId = failure.responseId;
|
|
2955
|
+
throw new ResponsesStreamFailure(failure, event.response);
|
|
2373
2956
|
}
|
|
2374
|
-
|
|
2375
|
-
if (
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
const error = event.response?.error;
|
|
2380
|
-
const details = event.response?.incomplete_details;
|
|
2381
|
-
output.responseId = event.response.id;
|
|
2382
|
-
output.stopReason = "error";
|
|
2383
|
-
output.errorMessage = error ? `${error.code || "unknown"}: ${error.message || "no message"}` : details?.reason ? `incomplete: ${details.reason}` : "Unknown error (no error details in response)";
|
|
2384
|
-
terminalResponseEvent = "failed";
|
|
2385
|
-
break;
|
|
2386
|
-
}
|
|
2387
|
-
if (terminalResponseEvent === "failed") return;
|
|
2388
|
-
if (streamingToolCalls.hasActive()) throw new Error("Responses stream ended with unresolved tool calls");
|
|
2389
|
-
if (!terminalResponseEvent) throw new Error("OpenAI Responses stream ended before a terminal response event");
|
|
2957
|
+
if (streamingToolCalls.hasActive()) throw new Error("Responses stream ended with unresolved tool calls");
|
|
2958
|
+
if (!terminalResponseEvent) throw new Error("OpenAI Responses stream ended before a terminal response event");
|
|
2959
|
+
} finally {
|
|
2960
|
+
for (const block of output.content) delete block.partialJson;
|
|
2961
|
+
}
|
|
2390
2962
|
}
|
|
2391
2963
|
//#endregion
|
|
2392
|
-
export {
|
|
2964
|
+
export { normalizeOpenAIStrictCompatSchema as $, normalizeResponsesFailedEvent as A, resolveReplayableResponsesMessageId as B, prepareOpenAIResponsesReasoningItemForReplay as C, applyServiceTierPricing as D, tagOpenAIResponsesReasoningReplayItem as E, summarizeResponsesFailedNoDetailsObservation as F, throwIfModelStreamAborted as G, createModelStreamCooperativeScheduler as H, summarizeResponsesPayload as I, isStrictOpenAIJsonSchemaCompatible as J, clearOpenAIToolSchemaCacheForTest as K, summarizeResponsesTools as L, stringifyRedactedEvent as M, stringifyRedactedPayload as N, buildResponsesFailedNoDetailsObservation as O, summarizeOpenAITransportError as P, findOpenAIStrictSchemaViolations as Q, AZURE_RESPONSES_FIRST_EVENT_TIMEOUT_MS as R, isInvalidEncryptedContentError as S, stripResponsesRequestEncryptedContent as T, log as U, GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP as V, resolvePromptCacheKey as W, normalizeStrictOpenAIJsonSchema as X, normalizeOpenAIStrictToolParameters as Y, resolveOpenAIProjectedToolsStrictToolFlag as Z, resolveResponsesMessageSnapshotCollapse as _, supportsOpenAITemperature as _t, resolveResponsesTerminalStopReason as a, LLAMACPP_GBNF_MAX_REPETITION_THRESHOLD as at, convertResponsesMessages as b, resolveModelPayloadDebugMode as bt, readResponsesToolCallItemIdentity as c, GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS as ct, OPENAI_RESPONSES_OUTPUT_TEXT_CONTENT_PART_TYPE as d, isOpenAIGpt55Model as dt, extractToolSchemaModelCompat as et, OPENAI_RESPONSES_OUTPUT_TEXT_DELTA_EVENT_TYPE as f, isOpenAIGpt56Model as ft, isResponsesTextDeltaEventType as g, supportsOpenAIReasoningEffort as gt, isResponsesTextContentPartType as h, resolveOpenAISupportedReasoningEfforts as ht, readResponsesReasoningTokens as i, stripUnsupportedSchemaKeywords as it, safeDebugValue as j, logResponsesFailedNoDetails as k, AZURE_RESPONSES_TEXT_CONTENT_PART_TYPE as l, cleanSchemaForGemini as lt, isAzureResponsesTextDeltaEventType as m, resolveOpenAIReasoningEffortForModel as mt, processResponsesStream as n, resolveUnsupportedToolSchemaKeywords as nt, observeResponsesStream as o, cleanSchemaForLlamacppGbnf as ot, isAzureResponsesTextDeltaEvent as p, normalizeOpenAIReasoningEffort as pt, findOpenAIStrictToolProjectionDiagnostics as q, mapResponsesTerminalUsage as r, shouldOmitEmptyArrayItems as rt, createResponsesToolCallTracker as s, findLlamacppGbnfSchemaViolations as st, ResponsesStreamFailure as t, normalizeToolParameterSchema as tt, AZURE_RESPONSES_TEXT_DELTA_EVENT_TYPE as u, isOpenAIGpt54MiniModel as ut, buildOpenAIResponsesReasoningReplayMetadata as v, uniqueStrings as vt, resolveAzureOpenAIApiVersion as w, createResponsesStreamWithEncryptedContentRetry as x, resolveModelSseDebugMode as xt, buildResponsesInputMessage as y, emitModelTransportDebug as yt, OPENAI_CODEX_RESPONSES_DEFAULT_INSTRUCTIONS as z };
|