@openclaw/ai 2026.8.1-beta.3 → 2026.8.2
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-B8KwTtlb.mjs → anthropic-DTGZ4VMY.mjs} +25 -23
- package/dist/{anthropic-compaction-replay-4Zx7-aCr.mjs → anthropic-compaction-replay-CKCDrZJv.mjs} +34 -35
- package/dist/{anthropic-payload-policy-CSx7MoKH.d.mts → anthropic-payload-policy-BTzmNC14.d.mts} +3 -2
- package/dist/{api-registry-DFxSnFqG.d.mts → api-registry-BbUHEKER.d.mts} +2 -1
- package/dist/{azure-openai-responses-Aa4mVcYG.mjs → azure-openai-responses-BmqaDXM1.mjs} +6 -5
- package/dist/{number-coercion-H9qHik3g.mjs → base64-CEFBpSkN.mjs} +73 -1
- package/dist/diagnostics.d.mts +2 -3
- package/dist/diagnostics.mjs +1 -1
- package/dist/{env-api-keys-DrgeBuva.mjs → env-api-keys-bktO00EJ.mjs} +2 -15
- package/dist/{event-stream-D91-ohD_.d.mts → event-stream-DcvAPSBe.d.mts} +1 -2
- package/dist/event-stream-pXT3FI6X.d.mts +1 -0
- package/dist/event-stream.d.mts +2 -1
- package/dist/{google-yDRnipeq.mjs → google-CqjVZMwo.mjs} +8 -6
- package/dist/{google-shared-M1dl64nz.mjs → google-shared-Bnqni5j3.mjs} +34 -25
- package/dist/{google-vertex-DCJHM_a5.mjs → google-vertex-D_WREEJi.mjs} +8 -6
- package/dist/{host-DF_yxOsA.mjs → host-BIaiBURL.mjs} +4 -8
- package/dist/{host-DJq_FGcz.d.mts → host-DKFUAmCn.d.mts} +3 -3
- package/dist/index-CefaUGSS.d.mts +111 -0
- package/dist/index.d.mts +9 -10
- package/dist/index.mjs +4 -5
- package/dist/internal/anthropic.d.mts +51 -49
- package/dist/internal/anthropic.mjs +5 -5
- package/dist/internal/openai-responses-payload-policy.d.mts +3 -3
- package/dist/internal/openai-responses-payload-policy.mjs +2 -3
- package/dist/internal/openai.d.mts +65 -63
- package/dist/internal/openai.mjs +10 -9
- package/dist/internal/retry-after.d.mts +2 -3
- package/dist/internal/runtime.d.mts +51 -40
- package/dist/internal/runtime.mjs +9 -9
- package/dist/internal/shared.d.mts +25 -25
- package/dist/internal/shared.mjs +3 -3
- package/dist/{json-parse-CDnesDM_.mjs → json-parse-BuAJEbdW.mjs} +18 -2
- package/dist/{mistral-D6hRXI7A.mjs → mistral-C24jVd6f.mjs} +34 -32
- package/dist/{openai-chatgpt-responses-C339VIfT.mjs → openai-chatgpt-responses-Czum0oXz.mjs} +38 -20
- package/dist/openai-completions-BfFwac6f.mjs +404 -0
- package/dist/{openai-completions-compat-B0KTleXp.d.mts → openai-completions-compat-SZhi0Lf4.d.mts} +3 -3
- package/dist/openai-completions-stream-DGOvau4J.mjs +1625 -0
- package/dist/{openai-responses-BXEcr-Tp.mjs → openai-responses-BAryVEJM.mjs} +11 -7
- package/dist/openai-responses-compaction-window-DO7yV7az.mjs +686 -0
- package/dist/{openai-responses-contracts-CD75pQ0m.d.mts → openai-responses-contracts-BthRXano.d.mts} +14 -5
- package/dist/{openai-responses-payload-policy-CNGFT9zr.d.mts → openai-responses-payload-policy-rLRPsSmB.d.mts} +1 -0
- package/dist/{openai-responses-prompt-observer-internal-BG-1AsBI.mjs → openai-responses-prompt-observer-internal--R0kZLOZ.mjs} +3 -2
- package/dist/{openai-responses-shared-Cvt5MgEm.mjs → openai-responses-shared-CxgdGBvm.mjs} +332 -1484
- package/dist/openai-stop-reason-Drnn_6Qj.mjs +28 -0
- package/dist/openai-tool-schema-CSMh5e9O.mjs +1697 -0
- package/dist/{provider-error-C8mBRJDl.mjs → provider-error-BI8kIViN.mjs} +14 -88
- package/dist/{provider-options-Cc5ZQ6Aa.d.mts → provider-options-DJd_9euj.d.mts} +13 -4
- package/dist/{provider-transcript-transform--gtf0ZbR.mjs → provider-transcript-transform-WvJmFUAf.mjs} +1 -1
- package/dist/provider-types.d.mts +18 -17
- package/dist/providers.d.mts +5 -6
- package/dist/providers.mjs +9 -9
- package/dist/{reasoning-tag-text-partitioner-5ygO2rZc.mjs → reasoning-tag-text-partitioner-BsUX44Tq.mjs} +30 -20
- package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
- package/dist/{sanitize-unicode-BP7HLLZn.mjs → sanitize-unicode-S6binQG-.mjs} +2 -9
- package/dist/simple-options-rdOPzr7Z.mjs +155 -0
- package/dist/{anthropic-BQXQudmA.mjs → src-C8U7lkoa.mjs} +75 -1
- package/dist/{stream-first-event-timeout-MK28puvq.mjs → stream-first-event-timeout-DcNjoFQE.mjs} +1 -1
- package/dist/{streaming-byte-guard-BrbkbwUu.mjs → streaming-byte-guard-CC-HMn_u.mjs} +5 -6
- package/dist/string-normalization--fwJ4S2q.mjs +16 -0
- package/dist/{tool-schema-json-projection-FEr-jOoZ.mjs → tool-schema-json-projection-BtZiml7r.mjs} +22 -32
- package/dist/transport-stream-shared-BbG7PThh.d.mts +138 -0
- package/dist/{simple-options-oqsXB6U3.mjs → transport-stream-shared-D8nQWCUo.mjs} +10 -118
- package/dist/{transport-utils-Bv0FHkd0.mjs → transport-utils-CtuS1Upe.mjs} +10 -10
- package/dist/transports.d.mts +130 -189
- package/dist/transports.mjs +227 -1249
- package/dist/{types-DTAP49HF.d.mts → types-Beemor2E.d.mts} +58 -24
- package/dist/types-COdhCX0U.d.mts +1 -0
- package/dist/types.d.mts +6 -5
- package/dist/types.mjs +3 -4
- package/dist/utf16-slice-qz3nsy87.mjs +84 -0
- package/dist/{validation-DYNNrU9u.d.mts → validation-CC6s7Smp.d.mts} +1 -2
- package/dist/{validation-B61OhAio.mjs → validation-CJZtym2g.mjs} +7 -5
- package/dist/validation.d.mts +1 -1
- package/dist/validation.mjs +1 -1
- package/package.json +6 -6
- package/dist/anthropic-BECQCNdF.d.mts +0 -98
- package/dist/event-stream-BVVgDSdq.d.mts +0 -1
- package/dist/headers-B_e4-1J0.mjs +0 -9
- package/dist/index-BVVgDSdq.d.mts +0 -1
- package/dist/openai-completions-D88orSJ9.mjs +0 -632
- package/dist/openai-responses-contracts-DvILoqBk.mjs +0 -247
- package/dist/openai-responses-payload-policy-BDo8CQsx.mjs +0 -208
- package/dist/openai-stop-reason-9OONKyAm.mjs +0 -579
- package/dist/openai-tool-projection-CHGLt03m.mjs +0 -426
- package/dist/provider-options-AvldWZt8.mjs +0 -21
- package/dist/record-coerce-DdXsgUd_.mjs +0 -23
- package/dist/src-D2H6yKkH.mjs +0 -2
- package/dist/stream-first-event-timeout-DvDeSucC.d.mts +0 -29
- package/dist/string-coerce-fsri9iCu.mjs +0 -34
- package/dist/types-BVVgDSdq.d.mts +0 -1
- package/dist/utf16-slice-CvGodqok.mjs +0 -29
|
@@ -0,0 +1,1697 @@
|
|
|
1
|
+
import { c as normalizeLowercaseStringOrEmpty, o as isRecord } from "./utf16-slice-qz3nsy87.mjs";
|
|
2
|
+
import { n as applyProviderReportedUsageCost, r as calculateCost } from "./sanitize-unicode-S6binQG-.mjs";
|
|
3
|
+
import { n as getAiTransportHost } from "./host-BIaiBURL.mjs";
|
|
4
|
+
import { r as uniqueValues, t as normalizeStringEntries } from "./string-normalization--fwJ4S2q.mjs";
|
|
5
|
+
import { n as clampOpenAIPromptCacheKey } from "./openai-prompt-cache-mZTCdRPo.mjs";
|
|
6
|
+
import { t as projectRuntimeToolInputSchema } from "./tool-schema-json-projection-BtZiml7r.mjs";
|
|
7
|
+
import { C as headersToRecord, _ as transportAbortError, f as notifyProviderHttpResponse } from "./transport-stream-shared-D8nQWCUo.mjs";
|
|
8
|
+
//#region packages/ai/src/transports/openai-transport-shared.ts
|
|
9
|
+
/** Shared options, usage shape, cache identity, ordering, and stream scheduling for OpenAI APIs. */
|
|
10
|
+
const MODEL_STREAM_COOPERATIVE_YIELD_INTERVAL_MS = 12;
|
|
11
|
+
const MODEL_STREAM_COOPERATIVE_YIELD_MAX_EVENTS = 64;
|
|
12
|
+
const GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP = "skip_thought_signature_validator";
|
|
13
|
+
const log = {
|
|
14
|
+
debug(message, data) {
|
|
15
|
+
getAiTransportHost().logDebug("openai-transport", () => ({
|
|
16
|
+
message,
|
|
17
|
+
data
|
|
18
|
+
}));
|
|
19
|
+
},
|
|
20
|
+
info(message, data) {
|
|
21
|
+
getAiTransportHost().logInfo("openai-transport", message, data);
|
|
22
|
+
},
|
|
23
|
+
warn(message, data) {
|
|
24
|
+
getAiTransportHost().logWarn("openai-transport", message, data);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function resolveOpenAIClientBaseUrl(model, baseUrl = model.baseUrl) {
|
|
28
|
+
if (baseUrl?.trim()) return baseUrl;
|
|
29
|
+
if (model.provider.trim().toLowerCase() === "openai") return;
|
|
30
|
+
throw new Error(`Provider "${model.provider}" requires an explicit base URL before using an OpenAI-compatible API. Reload provider metadata or configure an endpoint.`);
|
|
31
|
+
}
|
|
32
|
+
const EMPTY_OPENAI_COMPLETIONS_REASONING_BATCH = {
|
|
33
|
+
deltas: [],
|
|
34
|
+
mirroredThinking: [],
|
|
35
|
+
hasThinking: false,
|
|
36
|
+
hasVisibleText: false
|
|
37
|
+
};
|
|
38
|
+
const OPENAI_COMPLETIONS_REASONING_FIELDS = [
|
|
39
|
+
"reasoning_content",
|
|
40
|
+
"reasoning",
|
|
41
|
+
"reasoning_text"
|
|
42
|
+
];
|
|
43
|
+
function appendOpenAICompletionsReasoningDelta(batch, next) {
|
|
44
|
+
if (next.kind === "thinking") batch.hasThinking = true;
|
|
45
|
+
else batch.hasVisibleText = true;
|
|
46
|
+
const previous = batch.deltas[batch.deltas.length - 1];
|
|
47
|
+
if (!previous || previous.kind !== next.kind) {
|
|
48
|
+
batch.deltas.push(next);
|
|
49
|
+
if (next.kind === "thinking") batch.mirroredThinking.push(next.text);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (next.kind === "thinking" && previous.kind === "thinking") {
|
|
53
|
+
if (previous.signature !== next.signature) {
|
|
54
|
+
batch.deltas.push(next);
|
|
55
|
+
batch.mirroredThinking.push(next.text);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
previous.text += next.text;
|
|
59
|
+
batch.mirroredThinking[batch.mirroredThinking.length - 1] += next.text;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
previous.text += next.text;
|
|
63
|
+
}
|
|
64
|
+
function createOpenAICompletionsReasoningBatch() {
|
|
65
|
+
return {
|
|
66
|
+
deltas: [],
|
|
67
|
+
mirroredThinking: [],
|
|
68
|
+
hasThinking: false,
|
|
69
|
+
hasVisibleText: false
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function readOpenAICompletionsReasoningBatch(delta, visibleReasoningDetailTypes) {
|
|
73
|
+
let batch;
|
|
74
|
+
const reasoningDetails = delta.reasoning_details;
|
|
75
|
+
let usedReasoningThinkingDetails = false;
|
|
76
|
+
if (Array.isArray(reasoningDetails)) for (const item of reasoningDetails) {
|
|
77
|
+
if (!isRecord(item)) continue;
|
|
78
|
+
const detail = item;
|
|
79
|
+
if (typeof detail.text !== "string" || !detail.text) continue;
|
|
80
|
+
if (detail.type === "reasoning.text") {
|
|
81
|
+
usedReasoningThinkingDetails = true;
|
|
82
|
+
batch ??= createOpenAICompletionsReasoningBatch();
|
|
83
|
+
appendOpenAICompletionsReasoningDelta(batch, {
|
|
84
|
+
kind: "thinking",
|
|
85
|
+
signature: "reasoning_details",
|
|
86
|
+
text: detail.text
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (typeof detail.type === "string" && visibleReasoningDetailTypes.has(detail.type)) {
|
|
91
|
+
batch ??= createOpenAICompletionsReasoningBatch();
|
|
92
|
+
appendOpenAICompletionsReasoningDelta(batch, {
|
|
93
|
+
kind: "text",
|
|
94
|
+
text: detail.text,
|
|
95
|
+
source: "reasoning_detail"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!usedReasoningThinkingDetails) for (const field of OPENAI_COMPLETIONS_REASONING_FIELDS) {
|
|
100
|
+
const value = delta[field];
|
|
101
|
+
if (typeof value === "string" && value.length > 0) {
|
|
102
|
+
batch ??= createOpenAICompletionsReasoningBatch();
|
|
103
|
+
appendOpenAICompletionsReasoningDelta(batch, {
|
|
104
|
+
kind: "thinking",
|
|
105
|
+
signature: field,
|
|
106
|
+
text: value
|
|
107
|
+
});
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return batch ?? EMPTY_OPENAI_COMPLETIONS_REASONING_BATCH;
|
|
112
|
+
}
|
|
113
|
+
function parseOpenAICompletionsUsage(rawUsage, model, options) {
|
|
114
|
+
const cacheRead = rawUsage.prompt_tokens_details?.cached_tokens ?? rawUsage.prompt_cache_hit_tokens ?? 0;
|
|
115
|
+
const cacheWrite = rawUsage.prompt_tokens_details?.cache_write_tokens ?? rawUsage.prompt_tokens_details?.cache_creation_input_tokens ?? 0;
|
|
116
|
+
const input = Math.max(0, (rawUsage.prompt_tokens || 0) - cacheRead - cacheWrite);
|
|
117
|
+
const output = rawUsage.completion_tokens || 0;
|
|
118
|
+
const reasoningTokens = rawUsage.completion_tokens_details?.reasoning_tokens;
|
|
119
|
+
const usage = {
|
|
120
|
+
input,
|
|
121
|
+
output,
|
|
122
|
+
cacheRead,
|
|
123
|
+
cacheWrite,
|
|
124
|
+
...options?.includeReasoningTokens !== false && typeof reasoningTokens === "number" && Number.isFinite(reasoningTokens) ? { reasoningTokens } : {},
|
|
125
|
+
totalTokens: input + output + cacheRead + cacheWrite,
|
|
126
|
+
cost: {
|
|
127
|
+
input: 0,
|
|
128
|
+
output: 0,
|
|
129
|
+
cacheRead: 0,
|
|
130
|
+
cacheWrite: 0,
|
|
131
|
+
total: 0
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
calculateCost(model, usage);
|
|
135
|
+
applyProviderReportedUsageCost(usage, rawUsage.cost);
|
|
136
|
+
return usage;
|
|
137
|
+
}
|
|
138
|
+
function createOpenAIResponseHook(onResponse, response, model) {
|
|
139
|
+
return onResponse ? () => onResponse({
|
|
140
|
+
status: response.status,
|
|
141
|
+
headers: headersToRecord(response.headers)
|
|
142
|
+
}, model) : void 0;
|
|
143
|
+
}
|
|
144
|
+
function createOpenAIProviderAcceptanceHook(options, response, model) {
|
|
145
|
+
return () => notifyProviderHttpResponse({
|
|
146
|
+
options,
|
|
147
|
+
response,
|
|
148
|
+
model
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function throwIfModelStreamAborted(signal) {
|
|
152
|
+
if (signal?.aborted) throw transportAbortError(signal);
|
|
153
|
+
}
|
|
154
|
+
/** Measure one UTF-8 append without double-counting a surrogate pair split across chunks. */
|
|
155
|
+
function measureUtf8AppendBytes(bufferEndsWithHighSurrogate, chunk) {
|
|
156
|
+
let bytes = Buffer.byteLength(chunk, "utf8");
|
|
157
|
+
if (!chunk) return {
|
|
158
|
+
bytes,
|
|
159
|
+
endsWithHighSurrogate: bufferEndsWithHighSurrogate
|
|
160
|
+
};
|
|
161
|
+
const nextCodeUnit = chunk.charCodeAt(0);
|
|
162
|
+
if (bufferEndsWithHighSurrogate && nextCodeUnit >= 56320 && nextCodeUnit <= 57343) bytes -= 2;
|
|
163
|
+
const finalCodeUnit = chunk.charCodeAt(chunk.length - 1);
|
|
164
|
+
return {
|
|
165
|
+
bytes,
|
|
166
|
+
endsWithHighSurrogate: finalCodeUnit >= 55296 && finalCodeUnit <= 56319
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function createModelStreamCooperativeScheduler(signal) {
|
|
170
|
+
let lastYieldedAt = Date.now();
|
|
171
|
+
let eventsSinceYield = 0;
|
|
172
|
+
return { async afterEvent() {
|
|
173
|
+
throwIfModelStreamAborted(signal);
|
|
174
|
+
eventsSinceYield += 1;
|
|
175
|
+
const now = Date.now();
|
|
176
|
+
if (eventsSinceYield < MODEL_STREAM_COOPERATIVE_YIELD_MAX_EVENTS && now - lastYieldedAt < MODEL_STREAM_COOPERATIVE_YIELD_INTERVAL_MS) return;
|
|
177
|
+
eventsSinceYield = 0;
|
|
178
|
+
lastYieldedAt = now;
|
|
179
|
+
await new Promise((resolve) => {
|
|
180
|
+
setTimeout(resolve, 0);
|
|
181
|
+
});
|
|
182
|
+
throwIfModelStreamAborted(signal);
|
|
183
|
+
} };
|
|
184
|
+
}
|
|
185
|
+
function resolvePromptCacheKey(options, cacheRetention) {
|
|
186
|
+
if (cacheRetention === "none") return;
|
|
187
|
+
return clampOpenAIPromptCacheKey(options?.promptCacheKey ?? options?.sessionId);
|
|
188
|
+
}
|
|
189
|
+
function isOpenAICompletionsThinkingEnabled(effort) {
|
|
190
|
+
const normalized = effort.trim().toLowerCase();
|
|
191
|
+
return normalized !== "off" && normalized !== "none";
|
|
192
|
+
}
|
|
193
|
+
function readOpenAICompletionsContentDeltas(content, topLevelRefusal, mirroredThinking = []) {
|
|
194
|
+
let deltas = readOpenAICompletionsContentPartDeltas(content);
|
|
195
|
+
if (mirroredThinking.length > 0) {
|
|
196
|
+
const structuredThinking = deltas.filter((delta) => delta.kind === "thinking").map((delta) => delta.text);
|
|
197
|
+
const mirrorsCombinedThinking = structuredThinking.length > 1 && mirroredThinking.includes(structuredThinking.join(""));
|
|
198
|
+
deltas = deltas.filter((delta) => delta.kind !== "thinking" || !mirrorsCombinedThinking && !mirroredThinking.includes(delta.text));
|
|
199
|
+
}
|
|
200
|
+
if (typeof topLevelRefusal !== "string" || !topLevelRefusal) return deltas;
|
|
201
|
+
const structuredRefusals = deltas.filter((delta) => delta.kind === "text" && delta.source === "refusal").map((delta) => delta.text);
|
|
202
|
+
if (structuredRefusals.some((refusal) => refusal === topLevelRefusal) || structuredRefusals.length > 1 && structuredRefusals.join("") === topLevelRefusal) return deltas;
|
|
203
|
+
return [...deltas, {
|
|
204
|
+
kind: "text",
|
|
205
|
+
text: topLevelRefusal,
|
|
206
|
+
source: "refusal"
|
|
207
|
+
}];
|
|
208
|
+
}
|
|
209
|
+
function readOpenAICompletionsContentPartDeltas(content) {
|
|
210
|
+
if (typeof content === "string") return content ? [{
|
|
211
|
+
kind: "text",
|
|
212
|
+
text: content
|
|
213
|
+
}] : [];
|
|
214
|
+
if (Array.isArray(content)) return content.flatMap(readOpenAICompletionsContentPartDeltas);
|
|
215
|
+
if (!content || typeof content !== "object") return [];
|
|
216
|
+
const record = content;
|
|
217
|
+
const type = typeof record.type === "string" ? record.type.toLowerCase() : "";
|
|
218
|
+
const extractText = (value) => {
|
|
219
|
+
if (typeof value === "string") return value;
|
|
220
|
+
if (Array.isArray(value)) return value.map(extractText).join("");
|
|
221
|
+
if (value && typeof value === "object") {
|
|
222
|
+
const nested = value;
|
|
223
|
+
return extractText(nested.text ?? nested.content ?? nested.thinking ?? nested.refusal);
|
|
224
|
+
}
|
|
225
|
+
return "";
|
|
226
|
+
};
|
|
227
|
+
const text = extractText(record.text ?? record.content ?? record.thinking ?? record.refusal);
|
|
228
|
+
if (!text) return [];
|
|
229
|
+
if (type.includes("thinking") || type.includes("reasoning")) return [{
|
|
230
|
+
kind: "thinking",
|
|
231
|
+
text
|
|
232
|
+
}];
|
|
233
|
+
if (type === "refusal") return [{
|
|
234
|
+
kind: "text",
|
|
235
|
+
text,
|
|
236
|
+
source: "refusal"
|
|
237
|
+
}];
|
|
238
|
+
if (["text", "output_text"].includes(type) || type.endsWith(".output_text")) return [{
|
|
239
|
+
kind: "text",
|
|
240
|
+
text
|
|
241
|
+
}];
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region packages/ai/src/providers/openai-tool-projection.ts
|
|
246
|
+
function unreadableToolDiagnostic(toolIndex) {
|
|
247
|
+
return {
|
|
248
|
+
toolIndex,
|
|
249
|
+
violations: [`tool[${toolIndex}] is unreadable`]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/** Snapshots direct/custom tool descriptors before OpenAI payload construction. */
|
|
253
|
+
function projectOpenAITools(tools) {
|
|
254
|
+
let inputToolCount;
|
|
255
|
+
try {
|
|
256
|
+
inputToolCount = tools.length;
|
|
257
|
+
} catch {
|
|
258
|
+
return {
|
|
259
|
+
inputToolCount: 0,
|
|
260
|
+
tools: [],
|
|
261
|
+
diagnostics: [unreadableToolDiagnostic(0)]
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const projectedTools = [];
|
|
265
|
+
const diagnostics = [];
|
|
266
|
+
for (let toolIndex = 0; toolIndex < inputToolCount; toolIndex += 1) {
|
|
267
|
+
let tool;
|
|
268
|
+
try {
|
|
269
|
+
const candidate = tools[toolIndex];
|
|
270
|
+
if (!candidate) {
|
|
271
|
+
diagnostics.push(unreadableToolDiagnostic(toolIndex));
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
tool = candidate;
|
|
275
|
+
} catch {
|
|
276
|
+
diagnostics.push(unreadableToolDiagnostic(toolIndex));
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
let name;
|
|
280
|
+
try {
|
|
281
|
+
name = tool.name;
|
|
282
|
+
} catch {
|
|
283
|
+
diagnostics.push({
|
|
284
|
+
toolIndex,
|
|
285
|
+
violations: [`tool[${toolIndex}].name is unreadable`]
|
|
286
|
+
});
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (typeof name !== "string" || !name) {
|
|
290
|
+
diagnostics.push({
|
|
291
|
+
toolIndex,
|
|
292
|
+
violations: [`tool[${toolIndex}].name is empty`]
|
|
293
|
+
});
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
let parameters;
|
|
297
|
+
try {
|
|
298
|
+
parameters = tool.parameters;
|
|
299
|
+
} catch {
|
|
300
|
+
diagnostics.push({
|
|
301
|
+
toolIndex,
|
|
302
|
+
toolName: name,
|
|
303
|
+
violations: [`${name}.parameters is unreadable`]
|
|
304
|
+
});
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const schemaProjection = projectRuntimeToolInputSchema(parameters ?? {}, `${name}.parameters`);
|
|
308
|
+
if (!isRecord(schemaProjection.schema) || schemaProjection.violations.length > 0) {
|
|
309
|
+
diagnostics.push({
|
|
310
|
+
toolIndex,
|
|
311
|
+
toolName: name,
|
|
312
|
+
violations: schemaProjection.violations.length > 0 ? schemaProjection.violations : [`${name}.parameters must be a JSON object schema`]
|
|
313
|
+
});
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
let descriptionValue;
|
|
317
|
+
try {
|
|
318
|
+
descriptionValue = tool.description;
|
|
319
|
+
} catch {}
|
|
320
|
+
const description = typeof descriptionValue === "string" ? descriptionValue : void 0;
|
|
321
|
+
projectedTools.push({
|
|
322
|
+
toolIndex,
|
|
323
|
+
name,
|
|
324
|
+
...description !== void 0 ? { description } : {},
|
|
325
|
+
parameters: schemaProjection.schema
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
inputToolCount,
|
|
330
|
+
tools: projectedTools,
|
|
331
|
+
diagnostics
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function requireProjectedFunction(name, projection, choiceLabel) {
|
|
335
|
+
if (!projection.tools.some((tool) => tool.name === name)) throw new Error(`${choiceLabel} requested unavailable tool "${name}" after schema conversion`);
|
|
336
|
+
}
|
|
337
|
+
/** Keeps Responses tool choices aligned with surviving function schemas. */
|
|
338
|
+
function reconcileOpenAIResponsesToolChoice(choice, projection) {
|
|
339
|
+
if (choice === "auto") return projection.tools.length > 0 ? choice : void 0;
|
|
340
|
+
if (choice === "required") {
|
|
341
|
+
if (projection.tools.length === 0) throw new Error("OpenAI Responses tool_choice requires a tool, but no tools survived schema conversion");
|
|
342
|
+
return choice;
|
|
343
|
+
}
|
|
344
|
+
if (choice === "none" || !isRecord(choice)) return choice;
|
|
345
|
+
const choiceType = choice.type;
|
|
346
|
+
if (choiceType === "function") {
|
|
347
|
+
const functionName = choice.name;
|
|
348
|
+
if (typeof functionName !== "string") return choice;
|
|
349
|
+
requireProjectedFunction(functionName, projection, "OpenAI Responses tool_choice");
|
|
350
|
+
return {
|
|
351
|
+
type: "function",
|
|
352
|
+
name: functionName
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
if (choiceType !== "allowed_tools") return choice;
|
|
356
|
+
const mode = choice.mode;
|
|
357
|
+
const tools = choice.tools;
|
|
358
|
+
if (mode !== "auto" && mode !== "required" || !Array.isArray(tools)) return choice;
|
|
359
|
+
const normalizedAllowedTools = [];
|
|
360
|
+
for (const tool of tools) {
|
|
361
|
+
if (!isRecord(tool) || tool.type !== "function") {
|
|
362
|
+
normalizedAllowedTools.push(tool);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
const functionName = tool.name;
|
|
366
|
+
if (typeof functionName === "string" && projection.tools.some((projectedTool) => projectedTool.name === functionName)) normalizedAllowedTools.push({
|
|
367
|
+
type: "function",
|
|
368
|
+
name: functionName
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (normalizedAllowedTools.length === 0) {
|
|
372
|
+
if (mode === "auto") return "none";
|
|
373
|
+
throw new Error("OpenAI Responses tool_choice requires a tool, but no allowed tools survived schema conversion");
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
type: "allowed_tools",
|
|
377
|
+
mode,
|
|
378
|
+
tools: normalizedAllowedTools
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
/** Keeps Chat Completions tool choices aligned with surviving function schemas. */
|
|
382
|
+
function reconcileOpenAICompletionsToolChoice(choice, projection) {
|
|
383
|
+
if (choice === "auto") return projection.tools.length > 0 ? choice : void 0;
|
|
384
|
+
if (choice === "required") {
|
|
385
|
+
if (projection.tools.length === 0) throw new Error("OpenAI Chat Completions tool_choice requires a tool, but no tools survived schema conversion");
|
|
386
|
+
return choice;
|
|
387
|
+
}
|
|
388
|
+
if (choice === "none" || !isRecord(choice)) return choice;
|
|
389
|
+
const choiceType = choice.type;
|
|
390
|
+
if (choiceType === "custom") throw new Error("OpenAI Chat Completions custom tool_choice is unsupported because this adapter emits function tools only");
|
|
391
|
+
if (choiceType === "function") {
|
|
392
|
+
const functionChoice = choice.function;
|
|
393
|
+
if (!isRecord(functionChoice)) return choice;
|
|
394
|
+
const functionName = functionChoice.name;
|
|
395
|
+
if (typeof functionName !== "string") return choice;
|
|
396
|
+
requireProjectedFunction(functionName, projection, "OpenAI Chat Completions tool_choice");
|
|
397
|
+
return {
|
|
398
|
+
type: "function",
|
|
399
|
+
function: { name: functionName }
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (choiceType !== "allowed_tools") return choice;
|
|
403
|
+
const allowedConfig = choice.allowed_tools;
|
|
404
|
+
if (!isRecord(allowedConfig)) return choice;
|
|
405
|
+
const mode = allowedConfig.mode;
|
|
406
|
+
const tools = allowedConfig.tools;
|
|
407
|
+
if (mode !== "auto" && mode !== "required" || !Array.isArray(tools)) return choice;
|
|
408
|
+
const normalizedAllowedTools = [];
|
|
409
|
+
for (const tool of tools) {
|
|
410
|
+
if (!isRecord(tool) || tool.type !== "function") continue;
|
|
411
|
+
const functionChoice = tool.function;
|
|
412
|
+
const functionName = isRecord(functionChoice) ? functionChoice.name : void 0;
|
|
413
|
+
if (typeof functionName === "string" && projection.tools.some((projectedTool) => projectedTool.name === functionName)) normalizedAllowedTools.push({
|
|
414
|
+
type: "function",
|
|
415
|
+
function: { name: functionName }
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (normalizedAllowedTools.length === 0) {
|
|
419
|
+
if (mode === "auto") return "none";
|
|
420
|
+
throw new Error("OpenAI Chat Completions tool_choice requires a tool, but no allowed tools survived schema conversion");
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
type: "allowed_tools",
|
|
424
|
+
allowed_tools: {
|
|
425
|
+
mode,
|
|
426
|
+
tools: normalizedAllowedTools
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
//#endregion
|
|
431
|
+
//#region packages/ai/src/providers/clean-for-gemini.ts
|
|
432
|
+
const GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
433
|
+
"patternProperties",
|
|
434
|
+
"additionalProperties",
|
|
435
|
+
"$schema",
|
|
436
|
+
"$id",
|
|
437
|
+
"$ref",
|
|
438
|
+
"$defs",
|
|
439
|
+
"definitions",
|
|
440
|
+
"examples",
|
|
441
|
+
"minLength",
|
|
442
|
+
"maxLength",
|
|
443
|
+
"minimum",
|
|
444
|
+
"maximum",
|
|
445
|
+
"multipleOf",
|
|
446
|
+
"pattern",
|
|
447
|
+
"format",
|
|
448
|
+
"minItems",
|
|
449
|
+
"maxItems",
|
|
450
|
+
"uniqueItems",
|
|
451
|
+
"minProperties",
|
|
452
|
+
"maxProperties",
|
|
453
|
+
"not"
|
|
454
|
+
]);
|
|
455
|
+
const SCHEMA_META_KEYS = [
|
|
456
|
+
"description",
|
|
457
|
+
"title",
|
|
458
|
+
"default"
|
|
459
|
+
];
|
|
460
|
+
function copySchemaMeta$1(from, to) {
|
|
461
|
+
for (const key of SCHEMA_META_KEYS) if (key in from && from[key] !== void 0) to[key] = from[key];
|
|
462
|
+
}
|
|
463
|
+
function stringifyGeminiEnumValue(value) {
|
|
464
|
+
if (typeof value === "string") return value;
|
|
465
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
466
|
+
if (typeof value === "boolean") return String(value);
|
|
467
|
+
}
|
|
468
|
+
function cleanGeminiEnumValues(value) {
|
|
469
|
+
if (!Array.isArray(value)) return;
|
|
470
|
+
const values = value.flatMap((entry) => {
|
|
471
|
+
const stringified = stringifyGeminiEnumValue(entry);
|
|
472
|
+
return stringified === void 0 ? [] : [stringified];
|
|
473
|
+
});
|
|
474
|
+
const unique = [...new Set(values)];
|
|
475
|
+
return unique.length > 0 ? unique : void 0;
|
|
476
|
+
}
|
|
477
|
+
function tryFlattenLiteralAnyOf(variants) {
|
|
478
|
+
if (variants.length === 0) return null;
|
|
479
|
+
const allValues = [];
|
|
480
|
+
let commonType = null;
|
|
481
|
+
for (const variant of variants) {
|
|
482
|
+
if (!variant || typeof variant !== "object") return null;
|
|
483
|
+
const v = variant;
|
|
484
|
+
let literalValue;
|
|
485
|
+
if ("const" in v) literalValue = v.const;
|
|
486
|
+
else if (Array.isArray(v.enum) && v.enum.length === 1) literalValue = v.enum[0];
|
|
487
|
+
else return null;
|
|
488
|
+
const variantType = typeof v.type === "string" ? v.type : null;
|
|
489
|
+
if (!variantType) return null;
|
|
490
|
+
if (commonType === null) commonType = variantType;
|
|
491
|
+
else if (commonType !== variantType) return null;
|
|
492
|
+
allValues.push(literalValue);
|
|
493
|
+
}
|
|
494
|
+
if (commonType && allValues.length > 0) return {
|
|
495
|
+
type: commonType,
|
|
496
|
+
enum: allValues
|
|
497
|
+
};
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
function isNullSchema(variant) {
|
|
501
|
+
if (!variant || typeof variant !== "object" || Array.isArray(variant)) return false;
|
|
502
|
+
const record = variant;
|
|
503
|
+
if ("const" in record && record.const === null) return true;
|
|
504
|
+
if (Array.isArray(record.enum) && record.enum.length === 1) return record.enum[0] === null;
|
|
505
|
+
const typeValue = record.type;
|
|
506
|
+
if (typeValue === "null") return true;
|
|
507
|
+
if (Array.isArray(typeValue) && typeValue.length === 1 && typeValue[0] === "null") return true;
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
function stripNullVariants(variants) {
|
|
511
|
+
if (variants.length === 0) return {
|
|
512
|
+
variants,
|
|
513
|
+
stripped: false
|
|
514
|
+
};
|
|
515
|
+
const nonNull = variants.filter((variant) => !isNullSchema(variant));
|
|
516
|
+
return {
|
|
517
|
+
variants: nonNull,
|
|
518
|
+
stripped: nonNull.length !== variants.length
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function extendSchemaDefs$1(defs, schema) {
|
|
522
|
+
const defsEntry = schema.$defs && typeof schema.$defs === "object" && !Array.isArray(schema.$defs) ? schema.$defs : void 0;
|
|
523
|
+
const legacyDefsEntry = schema.definitions && typeof schema.definitions === "object" && !Array.isArray(schema.definitions) ? schema.definitions : void 0;
|
|
524
|
+
if (!defsEntry && !legacyDefsEntry) return defs;
|
|
525
|
+
const next = defs ? new Map(defs) : /* @__PURE__ */ new Map();
|
|
526
|
+
if (defsEntry) for (const [key, value] of Object.entries(defsEntry)) next.set(key, value);
|
|
527
|
+
if (legacyDefsEntry) for (const [key, value] of Object.entries(legacyDefsEntry)) next.set(key, value);
|
|
528
|
+
return next;
|
|
529
|
+
}
|
|
530
|
+
function decodeJsonPointerSegment$1(segment) {
|
|
531
|
+
return segment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
532
|
+
}
|
|
533
|
+
function tryResolveLocalRef$1(ref, defs) {
|
|
534
|
+
if (!defs) return;
|
|
535
|
+
const match = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
|
|
536
|
+
if (!match) return;
|
|
537
|
+
const name = decodeJsonPointerSegment$1(match[1] ?? "");
|
|
538
|
+
if (!name) return;
|
|
539
|
+
return defs.get(name);
|
|
540
|
+
}
|
|
541
|
+
function simplifyUnionVariants(params) {
|
|
542
|
+
const { obj, variants } = params;
|
|
543
|
+
const { variants: nonNullVariants, stripped } = stripNullVariants(variants);
|
|
544
|
+
const flattened = tryFlattenLiteralAnyOf(nonNullVariants);
|
|
545
|
+
if (flattened) {
|
|
546
|
+
const result = {
|
|
547
|
+
type: flattened.type,
|
|
548
|
+
enum: flattened.enum
|
|
549
|
+
};
|
|
550
|
+
copySchemaMeta$1(obj, result);
|
|
551
|
+
return {
|
|
552
|
+
kind: "simplified",
|
|
553
|
+
value: result
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
if (stripped && nonNullVariants.length === 1) {
|
|
557
|
+
const lone = nonNullVariants[0];
|
|
558
|
+
if (lone && typeof lone === "object" && !Array.isArray(lone)) {
|
|
559
|
+
const result = { ...lone };
|
|
560
|
+
copySchemaMeta$1(obj, result);
|
|
561
|
+
return {
|
|
562
|
+
kind: "simplified",
|
|
563
|
+
value: result
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
kind: "simplified",
|
|
568
|
+
value: lone
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
return {
|
|
572
|
+
kind: "variants",
|
|
573
|
+
value: stripped ? nonNullVariants : variants
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function sanitizeRequiredFields(schema) {
|
|
577
|
+
if (!Array.isArray(schema.required)) return schema;
|
|
578
|
+
if (!schema.properties || typeof schema.properties !== "object" || Array.isArray(schema.properties)) {
|
|
579
|
+
if (schema.type === "object") delete schema.required;
|
|
580
|
+
return schema;
|
|
581
|
+
}
|
|
582
|
+
const properties = schema.properties;
|
|
583
|
+
const required = schema.required.filter((key) => typeof key === "string" && Object.hasOwn(properties, key));
|
|
584
|
+
if (required.length > 0) schema.required = required;
|
|
585
|
+
else delete schema.required;
|
|
586
|
+
return schema;
|
|
587
|
+
}
|
|
588
|
+
function cleanSchemaForGeminiWithDefs(schema, defs, refStack) {
|
|
589
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
590
|
+
if (Array.isArray(schema)) return schema.map((item) => cleanSchemaForGeminiWithDefs(item, defs, refStack));
|
|
591
|
+
const obj = schema;
|
|
592
|
+
const nextDefs = extendSchemaDefs$1(defs, obj);
|
|
593
|
+
const refValue = typeof obj.$ref === "string" ? obj.$ref : void 0;
|
|
594
|
+
if (refValue) {
|
|
595
|
+
if (refStack?.has(refValue)) return {};
|
|
596
|
+
const resolved = tryResolveLocalRef$1(refValue, nextDefs);
|
|
597
|
+
if (resolved) {
|
|
598
|
+
const nextRefStack = refStack ? new Set(refStack) : /* @__PURE__ */ new Set();
|
|
599
|
+
nextRefStack.add(refValue);
|
|
600
|
+
const cleaned = cleanSchemaForGeminiWithDefs(resolved, nextDefs, nextRefStack);
|
|
601
|
+
if (!cleaned || typeof cleaned !== "object" || Array.isArray(cleaned)) return cleaned;
|
|
602
|
+
const result = { ...cleaned };
|
|
603
|
+
copySchemaMeta$1(obj, result);
|
|
604
|
+
return result;
|
|
605
|
+
}
|
|
606
|
+
const result = {};
|
|
607
|
+
copySchemaMeta$1(obj, result);
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
const hasAnyOf = "anyOf" in obj && Array.isArray(obj.anyOf);
|
|
611
|
+
const hasOneOf = "oneOf" in obj && Array.isArray(obj.oneOf);
|
|
612
|
+
let cleanedAnyOf = hasAnyOf ? obj.anyOf.map((variant) => cleanSchemaForGeminiWithDefs(variant, nextDefs, refStack)) : void 0;
|
|
613
|
+
let cleanedOneOf = hasOneOf ? obj.oneOf.map((variant) => cleanSchemaForGeminiWithDefs(variant, nextDefs, refStack)) : void 0;
|
|
614
|
+
if (hasAnyOf) {
|
|
615
|
+
const simplified = simplifyUnionVariants({
|
|
616
|
+
obj,
|
|
617
|
+
variants: cleanedAnyOf ?? []
|
|
618
|
+
});
|
|
619
|
+
if (simplified.kind === "simplified") return simplified.value;
|
|
620
|
+
cleanedAnyOf = simplified.value;
|
|
621
|
+
}
|
|
622
|
+
if (hasOneOf) {
|
|
623
|
+
const simplified = simplifyUnionVariants({
|
|
624
|
+
obj,
|
|
625
|
+
variants: cleanedOneOf ?? []
|
|
626
|
+
});
|
|
627
|
+
if (simplified.kind === "simplified") return simplified.value;
|
|
628
|
+
cleanedOneOf = simplified.value;
|
|
629
|
+
}
|
|
630
|
+
const cleaned = {};
|
|
631
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
632
|
+
if (GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS.has(key)) continue;
|
|
633
|
+
if (key === "const") {
|
|
634
|
+
const enumValues = cleanGeminiEnumValues([value]);
|
|
635
|
+
if (enumValues) cleaned.enum = enumValues;
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (key === "enum") {
|
|
639
|
+
const enumValues = cleanGeminiEnumValues(value);
|
|
640
|
+
if (enumValues) cleaned.enum = enumValues;
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (key === "required" && Array.isArray(value) && value.length === 0) continue;
|
|
644
|
+
if (key === "type" && (hasAnyOf || hasOneOf)) continue;
|
|
645
|
+
if (key === "type" && Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
646
|
+
const types = value.filter((entry) => entry !== "null");
|
|
647
|
+
cleaned.type = types.length === 1 ? types[0] : types;
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (key === "properties") {
|
|
651
|
+
if (value && typeof value === "object" && !Array.isArray(value)) cleaned[key] = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, cleanSchemaForGeminiWithDefs(v, nextDefs, refStack)]));
|
|
652
|
+
else cleaned[key] = {};
|
|
653
|
+
} else if (key === "items" && value) {
|
|
654
|
+
if (Array.isArray(value)) cleaned[key] = value.map((entry) => cleanSchemaForGeminiWithDefs(entry, nextDefs, refStack));
|
|
655
|
+
else if (typeof value === "object") cleaned[key] = cleanSchemaForGeminiWithDefs(value, nextDefs, refStack);
|
|
656
|
+
else cleaned[key] = value;
|
|
657
|
+
} else if (key === "anyOf" && Array.isArray(value)) cleaned[key] = cleanedAnyOf ?? value.map((variant) => cleanSchemaForGeminiWithDefs(variant, nextDefs, refStack));
|
|
658
|
+
else if (key === "oneOf" && Array.isArray(value)) cleaned[key] = cleanedOneOf ?? value.map((variant) => cleanSchemaForGeminiWithDefs(variant, nextDefs, refStack));
|
|
659
|
+
else if (key === "allOf" && Array.isArray(value)) cleaned[key] = value.map((variant) => cleanSchemaForGeminiWithDefs(variant, nextDefs, refStack));
|
|
660
|
+
else cleaned[key] = value;
|
|
661
|
+
}
|
|
662
|
+
if (cleaned.anyOf && Array.isArray(cleaned.anyOf)) {
|
|
663
|
+
const flattened = flattenUnionFallback(cleaned, cleaned.anyOf);
|
|
664
|
+
if (flattened) return sanitizeRequiredFields(flattened);
|
|
665
|
+
}
|
|
666
|
+
if (cleaned.oneOf && Array.isArray(cleaned.oneOf)) {
|
|
667
|
+
const flattened = flattenUnionFallback(cleaned, cleaned.oneOf);
|
|
668
|
+
if (flattened) return sanitizeRequiredFields(flattened);
|
|
669
|
+
}
|
|
670
|
+
return sanitizeRequiredFields(cleaned);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Last-resort flattening for anyOf/oneOf arrays that could not be simplified
|
|
674
|
+
* by `simplifyUnionVariants`. Picks a representative type so the schema is
|
|
675
|
+
* accepted by Google's restricted JSON Schema validation.
|
|
676
|
+
*/
|
|
677
|
+
function flattenUnionFallback(obj, variants) {
|
|
678
|
+
const objects = variants.filter((v) => Boolean(v) && typeof v === "object");
|
|
679
|
+
if (objects.length === 0) return;
|
|
680
|
+
const types = new Set(objects.map((v) => v.type).filter(Boolean));
|
|
681
|
+
if (objects.length === 1) {
|
|
682
|
+
const merged = { ...objects[0] };
|
|
683
|
+
copySchemaMeta$1(obj, merged);
|
|
684
|
+
return merged;
|
|
685
|
+
}
|
|
686
|
+
if (types.size === 1) {
|
|
687
|
+
const merged = { type: Array.from(types)[0] };
|
|
688
|
+
copySchemaMeta$1(obj, merged);
|
|
689
|
+
return merged;
|
|
690
|
+
}
|
|
691
|
+
const first = objects[0];
|
|
692
|
+
if (first?.type) {
|
|
693
|
+
const merged = { type: first.type };
|
|
694
|
+
copySchemaMeta$1(obj, merged);
|
|
695
|
+
return merged;
|
|
696
|
+
}
|
|
697
|
+
const merged = {};
|
|
698
|
+
copySchemaMeta$1(obj, merged);
|
|
699
|
+
return merged;
|
|
700
|
+
}
|
|
701
|
+
function cleanSchemaForGemini(schema) {
|
|
702
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
703
|
+
if (Array.isArray(schema)) return schema.map(cleanSchemaForGemini);
|
|
704
|
+
return cleanSchemaForGeminiWithDefs(schema, extendSchemaDefs$1(void 0, schema), void 0);
|
|
705
|
+
}
|
|
706
|
+
//#endregion
|
|
707
|
+
//#region packages/ai/src/providers/clean-for-llamacpp-gbnf.ts
|
|
708
|
+
/** llama.cpp rejects grammar repetitions whose expanded rule count reaches 2000. */
|
|
709
|
+
const LLAMACPP_GBNF_MAX_REPETITION_THRESHOLD = 2e3;
|
|
710
|
+
const SCHEMA_MAP_KEYS$2 = /* @__PURE__ */ new Set([
|
|
711
|
+
"$defs",
|
|
712
|
+
"definitions",
|
|
713
|
+
"dependentSchemas",
|
|
714
|
+
"patternProperties",
|
|
715
|
+
"properties"
|
|
716
|
+
]);
|
|
717
|
+
const SCHEMA_CHILD_KEYS = /* @__PURE__ */ new Set([
|
|
718
|
+
"additionalItems",
|
|
719
|
+
"additionalProperties",
|
|
720
|
+
"allOf",
|
|
721
|
+
"anyOf",
|
|
722
|
+
"contains",
|
|
723
|
+
"else",
|
|
724
|
+
"if",
|
|
725
|
+
"items",
|
|
726
|
+
"not",
|
|
727
|
+
"oneOf",
|
|
728
|
+
"prefixItems",
|
|
729
|
+
"propertyNames",
|
|
730
|
+
"then",
|
|
731
|
+
"unevaluatedItems",
|
|
732
|
+
"unevaluatedProperties"
|
|
733
|
+
]);
|
|
734
|
+
function cleanSchemaNode(node) {
|
|
735
|
+
if (Array.isArray(node)) {
|
|
736
|
+
let changed = false;
|
|
737
|
+
const entries = node.map((entry) => {
|
|
738
|
+
const next = cleanSchemaNode(entry);
|
|
739
|
+
changed ||= next !== entry;
|
|
740
|
+
return next;
|
|
741
|
+
});
|
|
742
|
+
return changed ? entries : node;
|
|
743
|
+
}
|
|
744
|
+
if (!isRecord(node)) return node;
|
|
745
|
+
let changed = false;
|
|
746
|
+
const cleaned = {};
|
|
747
|
+
for (const [key, value] of Object.entries(node)) {
|
|
748
|
+
if (key === "pattern") {
|
|
749
|
+
changed = true;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (key === "maxLength" && typeof value === "number" && value >= 2e3) {
|
|
753
|
+
changed = true;
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
let next = value;
|
|
757
|
+
if (SCHEMA_MAP_KEYS$2.has(key) && isRecord(value)) {
|
|
758
|
+
let mapChanged = false;
|
|
759
|
+
next = Object.fromEntries(Object.entries(value).map(([childKey, childValue]) => {
|
|
760
|
+
const cleanedChild = cleanSchemaNode(childValue);
|
|
761
|
+
mapChanged ||= cleanedChild !== childValue;
|
|
762
|
+
return [childKey, cleanedChild];
|
|
763
|
+
}));
|
|
764
|
+
if (!mapChanged) next = value;
|
|
765
|
+
} else if (SCHEMA_CHILD_KEYS.has(key)) next = cleanSchemaNode(value);
|
|
766
|
+
cleaned[key] = next;
|
|
767
|
+
changed ||= next !== value;
|
|
768
|
+
}
|
|
769
|
+
return changed ? cleaned : node;
|
|
770
|
+
}
|
|
771
|
+
function collectSchemaViolations(node, path, violations) {
|
|
772
|
+
if (Array.isArray(node)) {
|
|
773
|
+
node.forEach((entry, index) => collectSchemaViolations(entry, `${path}[${index}]`, violations));
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (!isRecord(node)) return;
|
|
777
|
+
if ("pattern" in node) violations.push(`${path}.pattern`);
|
|
778
|
+
if (typeof node.maxLength === "number" && node.maxLength >= 2e3) violations.push(`${path}.maxLength`);
|
|
779
|
+
for (const [key, value] of Object.entries(node)) if (SCHEMA_MAP_KEYS$2.has(key) && isRecord(value)) for (const [childKey, childValue] of Object.entries(value)) collectSchemaViolations(childValue, `${path}.${key}.${childKey}`, violations);
|
|
780
|
+
else if (SCHEMA_CHILD_KEYS.has(key)) collectSchemaViolations(value, `${path}.${key}`, violations);
|
|
781
|
+
}
|
|
782
|
+
/** Removes JSON Schema constraints that llama.cpp cannot compile into GBNF. */
|
|
783
|
+
function cleanSchemaForLlamacppGbnf(schema) {
|
|
784
|
+
return cleanSchemaNode(schema);
|
|
785
|
+
}
|
|
786
|
+
/** Reports schema paths that llama.cpp cannot compile into GBNF. */
|
|
787
|
+
function findLlamacppGbnfSchemaViolations(schema, path) {
|
|
788
|
+
const violations = [];
|
|
789
|
+
collectSchemaViolations(schema, path, violations);
|
|
790
|
+
return violations;
|
|
791
|
+
}
|
|
792
|
+
//#endregion
|
|
793
|
+
//#region packages/ai/src/providers/schema-keyword-strip.ts
|
|
794
|
+
const SCHEMA_MAP_KEYS$1 = /* @__PURE__ */ new Set([
|
|
795
|
+
"$defs",
|
|
796
|
+
"definitions",
|
|
797
|
+
"dependentSchemas",
|
|
798
|
+
"dependencies",
|
|
799
|
+
"patternProperties",
|
|
800
|
+
"properties"
|
|
801
|
+
]);
|
|
802
|
+
/** Containers whose value is a single nested schema. */
|
|
803
|
+
const SCHEMA_OBJECT_KEYS$1 = /* @__PURE__ */ new Set([
|
|
804
|
+
"additionalItems",
|
|
805
|
+
"additionalProperties",
|
|
806
|
+
"contains",
|
|
807
|
+
"contentSchema",
|
|
808
|
+
"else",
|
|
809
|
+
"if",
|
|
810
|
+
"items",
|
|
811
|
+
"not",
|
|
812
|
+
"propertyNames",
|
|
813
|
+
"then",
|
|
814
|
+
"unevaluatedItems",
|
|
815
|
+
"unevaluatedProperties"
|
|
816
|
+
]);
|
|
817
|
+
/** Containers whose value is a list of nested schemas. */
|
|
818
|
+
const SCHEMA_ARRAY_KEYS$1 = /* @__PURE__ */ new Set([
|
|
819
|
+
"allOf",
|
|
820
|
+
"anyOf",
|
|
821
|
+
"items",
|
|
822
|
+
"oneOf",
|
|
823
|
+
"prefixItems"
|
|
824
|
+
]);
|
|
825
|
+
/** Recursively remove schema keywords unsupported by a target provider/tool surface. */
|
|
826
|
+
function stripUnsupportedSchemaKeywords(schema, unsupportedKeywords) {
|
|
827
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
828
|
+
if (Array.isArray(schema)) return schema.map((entry) => stripUnsupportedSchemaKeywords(entry, unsupportedKeywords));
|
|
829
|
+
const obj = schema;
|
|
830
|
+
const cleaned = {};
|
|
831
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
832
|
+
if (unsupportedKeywords.has(key)) continue;
|
|
833
|
+
if (SCHEMA_MAP_KEYS$1.has(key) && value && typeof value === "object" && !Array.isArray(value)) {
|
|
834
|
+
cleaned[key] = Object.fromEntries(Object.entries(value).map(([childKey, childValue]) => [childKey, stripUnsupportedSchemaKeywords(childValue, unsupportedKeywords)]));
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
if (SCHEMA_ARRAY_KEYS$1.has(key) && Array.isArray(value)) {
|
|
838
|
+
cleaned[key] = value.map((entry) => stripUnsupportedSchemaKeywords(entry, unsupportedKeywords));
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (SCHEMA_OBJECT_KEYS$1.has(key) && value && typeof value === "object") {
|
|
842
|
+
cleaned[key] = stripUnsupportedSchemaKeywords(value, unsupportedKeywords);
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
cleaned[key] = value;
|
|
846
|
+
}
|
|
847
|
+
return cleaned;
|
|
848
|
+
}
|
|
849
|
+
//#endregion
|
|
850
|
+
//#region packages/ai/src/providers/agent-tools-parameter-schema.ts
|
|
851
|
+
/**
|
|
852
|
+
* Normalizes model-facing tool parameter schemas across provider quirks.
|
|
853
|
+
* Handles local JSON Schema refs, OpenAPI nullable syntax, top-level unions,
|
|
854
|
+
* and provider-specific unsupported keyword stripping.
|
|
855
|
+
*/
|
|
856
|
+
/** Extracts the compat record whether callers pass a model (`{ compat }`) or the compat itself. */
|
|
857
|
+
function extractToolSchemaModelCompat(modelOrCompat) {
|
|
858
|
+
if (!modelOrCompat || typeof modelOrCompat !== "object") return;
|
|
859
|
+
if ("compat" in modelOrCompat) {
|
|
860
|
+
const compat = modelOrCompat.compat;
|
|
861
|
+
return compat && typeof compat === "object" ? compat : void 0;
|
|
862
|
+
}
|
|
863
|
+
return modelOrCompat;
|
|
864
|
+
}
|
|
865
|
+
/** JSON Schema keywords this model/provider rejects in tool schemas. */
|
|
866
|
+
function resolveUnsupportedToolSchemaKeywords(modelOrCompat) {
|
|
867
|
+
const keywords = extractToolSchemaModelCompat(modelOrCompat)?.unsupportedToolSchemaKeywords ?? [];
|
|
868
|
+
return new Set(normalizeStringEntries(keywords.filter((keyword) => typeof keyword === "string")));
|
|
869
|
+
}
|
|
870
|
+
/** Whether empty `items: {}` on array schemas must be omitted for this model/provider. */
|
|
871
|
+
function shouldOmitEmptyArrayItems(modelOrCompat) {
|
|
872
|
+
return extractToolSchemaModelCompat(modelOrCompat)?.omitEmptyArrayItems === true;
|
|
873
|
+
}
|
|
874
|
+
const MAX_TOOL_PARAMETER_SCHEMA_CACHE_ENTRIES_PER_SCHEMA = 8;
|
|
875
|
+
const toolParameterSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
876
|
+
function resolveToolParameterSchemaCacheKey(options) {
|
|
877
|
+
const normalizedProvider = normalizeLowercaseStringOrEmpty(options?.modelProvider);
|
|
878
|
+
const normalizedModelId = normalizeLowercaseStringOrEmpty(options?.modelId);
|
|
879
|
+
const toolSchemaProfile = normalizeLowercaseStringOrEmpty(options?.modelCompat?.toolSchemaProfile);
|
|
880
|
+
const unsupportedKeywords = Array.from(resolveUnsupportedToolSchemaKeywords(options?.modelCompat)).toSorted();
|
|
881
|
+
const omitEmptyArrayItems = shouldOmitEmptyArrayItems(options?.modelCompat);
|
|
882
|
+
return JSON.stringify([
|
|
883
|
+
normalizedProvider,
|
|
884
|
+
normalizedModelId,
|
|
885
|
+
toolSchemaProfile,
|
|
886
|
+
unsupportedKeywords,
|
|
887
|
+
omitEmptyArrayItems
|
|
888
|
+
]);
|
|
889
|
+
}
|
|
890
|
+
function getCachedToolParameterSchema(schema, key) {
|
|
891
|
+
return toolParameterSchemaCache.get(schema)?.find((entry) => entry.key === key)?.value;
|
|
892
|
+
}
|
|
893
|
+
function rememberCachedToolParameterSchema(schema, key, value) {
|
|
894
|
+
const entries = toolParameterSchemaCache.get(schema) ?? [];
|
|
895
|
+
toolParameterSchemaCache.set(schema, [{
|
|
896
|
+
key,
|
|
897
|
+
value
|
|
898
|
+
}, ...entries.filter((entry) => entry.key !== key)].slice(0, MAX_TOOL_PARAMETER_SCHEMA_CACHE_ENTRIES_PER_SCHEMA));
|
|
899
|
+
return value;
|
|
900
|
+
}
|
|
901
|
+
function isGeminiModelId(modelId) {
|
|
902
|
+
return /(?:^|[/:])gemini(?:$|[-/:.])/.test(modelId);
|
|
903
|
+
}
|
|
904
|
+
function extractEnumValues(schema) {
|
|
905
|
+
if (!schema || typeof schema !== "object") return;
|
|
906
|
+
const record = schema;
|
|
907
|
+
if (Array.isArray(record.enum)) return record.enum;
|
|
908
|
+
if ("const" in record) return [record.const];
|
|
909
|
+
const variants = Array.isArray(record.anyOf) ? record.anyOf : Array.isArray(record.oneOf) ? record.oneOf : null;
|
|
910
|
+
if (variants) {
|
|
911
|
+
const values = variants.flatMap((variant) => {
|
|
912
|
+
return extractEnumValues(variant) ?? [];
|
|
913
|
+
});
|
|
914
|
+
return values.length > 0 ? values : void 0;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function mergePropertySchemas(existing, incoming) {
|
|
918
|
+
if (!existing) return incoming;
|
|
919
|
+
if (!incoming) return existing;
|
|
920
|
+
const existingEnum = extractEnumValues(existing);
|
|
921
|
+
const incomingEnum = extractEnumValues(incoming);
|
|
922
|
+
if (existingEnum || incomingEnum) {
|
|
923
|
+
const values = uniqueValues([...existingEnum ?? [], ...incomingEnum ?? []]);
|
|
924
|
+
const merged = {};
|
|
925
|
+
for (const source of [existing, incoming]) {
|
|
926
|
+
if (!source || typeof source !== "object") continue;
|
|
927
|
+
const record = source;
|
|
928
|
+
for (const key of [
|
|
929
|
+
"title",
|
|
930
|
+
"description",
|
|
931
|
+
"default"
|
|
932
|
+
]) if (!(key in merged) && key in record) merged[key] = record[key];
|
|
933
|
+
}
|
|
934
|
+
const types = new Set(values.map((value) => typeof value));
|
|
935
|
+
if (types.size === 1) merged.type = Array.from(types)[0];
|
|
936
|
+
merged.enum = values;
|
|
937
|
+
return merged;
|
|
938
|
+
}
|
|
939
|
+
return existing;
|
|
940
|
+
}
|
|
941
|
+
function setOwnSchemaProperty(target, key, value) {
|
|
942
|
+
Object.defineProperty(target, key, {
|
|
943
|
+
value,
|
|
944
|
+
enumerable: true,
|
|
945
|
+
configurable: true,
|
|
946
|
+
writable: true
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
function hasTopLevelArrayKeyword(schemaRecord, key) {
|
|
950
|
+
return Array.isArray(schemaRecord[key]);
|
|
951
|
+
}
|
|
952
|
+
function getFlattenableVariantKey(schemaRecord) {
|
|
953
|
+
if (hasTopLevelArrayKeyword(schemaRecord, "anyOf")) return "anyOf";
|
|
954
|
+
if (hasTopLevelArrayKeyword(schemaRecord, "oneOf")) return "oneOf";
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
function getTopLevelConditionalKey(schemaRecord) {
|
|
958
|
+
return getFlattenableVariantKey(schemaRecord) ?? (hasTopLevelArrayKeyword(schemaRecord, "allOf") ? "allOf" : null);
|
|
959
|
+
}
|
|
960
|
+
function hasTopLevelObjectSchema(schemaRecord, conditionalKey) {
|
|
961
|
+
return schemaRecord.type === "object" && isRecord(schemaRecord.properties) && conditionalKey === null;
|
|
962
|
+
}
|
|
963
|
+
function isObjectLikeSchemaMissingType(schemaRecord, conditionalKey) {
|
|
964
|
+
return !("type" in schemaRecord) && (isRecord(schemaRecord.properties) || Array.isArray(schemaRecord.required)) && conditionalKey === null;
|
|
965
|
+
}
|
|
966
|
+
function isTypedObjectSchemaMissingValidProperties(schemaRecord, conditionalKey) {
|
|
967
|
+
return schemaRecord.type === "object" && !isRecord(schemaRecord.properties) && conditionalKey === null;
|
|
968
|
+
}
|
|
969
|
+
function isTrulyEmptySchema(schemaRecord) {
|
|
970
|
+
return Object.keys(schemaRecord).length === 0;
|
|
971
|
+
}
|
|
972
|
+
function normalizeArraySchemasMissingItems(schema) {
|
|
973
|
+
if (!isRecord(schema)) return schema;
|
|
974
|
+
let changed = false;
|
|
975
|
+
const nextSchema = { ...schema };
|
|
976
|
+
if (nextSchema.type === "array" && nextSchema.items === void 0) {
|
|
977
|
+
nextSchema.items = {};
|
|
978
|
+
changed = true;
|
|
979
|
+
}
|
|
980
|
+
const normalizeSchemaValue = (key) => {
|
|
981
|
+
if (!(key in nextSchema)) return;
|
|
982
|
+
const value = nextSchema[key];
|
|
983
|
+
if (Array.isArray(value)) {
|
|
984
|
+
const normalized = value.map(normalizeArraySchemasMissingItems);
|
|
985
|
+
if (normalized.some((entry, index) => entry !== value[index])) {
|
|
986
|
+
nextSchema[key] = normalized;
|
|
987
|
+
changed = true;
|
|
988
|
+
}
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const normalized = normalizeArraySchemasMissingItems(value);
|
|
992
|
+
if (normalized !== value) {
|
|
993
|
+
nextSchema[key] = normalized;
|
|
994
|
+
changed = true;
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
for (const key of [
|
|
998
|
+
"items",
|
|
999
|
+
"contains",
|
|
1000
|
+
"additionalProperties",
|
|
1001
|
+
"propertyNames",
|
|
1002
|
+
"not",
|
|
1003
|
+
"if",
|
|
1004
|
+
"then",
|
|
1005
|
+
"else"
|
|
1006
|
+
]) normalizeSchemaValue(key);
|
|
1007
|
+
for (const key of [
|
|
1008
|
+
"anyOf",
|
|
1009
|
+
"oneOf",
|
|
1010
|
+
"allOf",
|
|
1011
|
+
"prefixItems"
|
|
1012
|
+
]) normalizeSchemaValue(key);
|
|
1013
|
+
for (const key of [
|
|
1014
|
+
"properties",
|
|
1015
|
+
"patternProperties",
|
|
1016
|
+
"dependentSchemas",
|
|
1017
|
+
"$defs",
|
|
1018
|
+
"definitions"
|
|
1019
|
+
]) {
|
|
1020
|
+
const value = nextSchema[key];
|
|
1021
|
+
if (!isRecord(value)) continue;
|
|
1022
|
+
let entriesChanged = false;
|
|
1023
|
+
const normalizedEntries = Object.entries(value).map(([entryKey, entryValue]) => {
|
|
1024
|
+
const normalizedEntryValue = normalizeArraySchemasMissingItems(entryValue);
|
|
1025
|
+
if (normalizedEntryValue !== entryValue) entriesChanged = true;
|
|
1026
|
+
return [entryKey, normalizedEntryValue];
|
|
1027
|
+
});
|
|
1028
|
+
if (entriesChanged) {
|
|
1029
|
+
nextSchema[key] = Object.fromEntries(normalizedEntries);
|
|
1030
|
+
changed = true;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return changed ? nextSchema : schema;
|
|
1034
|
+
}
|
|
1035
|
+
function schemaAllowsArrayType(schema) {
|
|
1036
|
+
const type = schema.type;
|
|
1037
|
+
return type === "array" || Array.isArray(type) && type.includes("array");
|
|
1038
|
+
}
|
|
1039
|
+
const ARRAY_ITEMS_SCHEMA_OBJECT_KEYS = /* @__PURE__ */ new Set([
|
|
1040
|
+
"additionalProperties",
|
|
1041
|
+
"contains",
|
|
1042
|
+
"else",
|
|
1043
|
+
"if",
|
|
1044
|
+
"items",
|
|
1045
|
+
"not",
|
|
1046
|
+
"propertyNames",
|
|
1047
|
+
"then"
|
|
1048
|
+
]);
|
|
1049
|
+
const ARRAY_ITEMS_SCHEMA_ARRAY_KEYS = /* @__PURE__ */ new Set([
|
|
1050
|
+
"allOf",
|
|
1051
|
+
"anyOf",
|
|
1052
|
+
"oneOf",
|
|
1053
|
+
"prefixItems"
|
|
1054
|
+
]);
|
|
1055
|
+
const ARRAY_ITEMS_SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set([
|
|
1056
|
+
"$defs",
|
|
1057
|
+
"definitions",
|
|
1058
|
+
"dependentSchemas",
|
|
1059
|
+
"patternProperties",
|
|
1060
|
+
"properties"
|
|
1061
|
+
]);
|
|
1062
|
+
function stripEmptyArrayItemsFromArraySchemas(schema) {
|
|
1063
|
+
if (Array.isArray(schema)) {
|
|
1064
|
+
let changed = false;
|
|
1065
|
+
const entries = schema.map((entry) => {
|
|
1066
|
+
const next = stripEmptyArrayItemsFromArraySchemas(entry);
|
|
1067
|
+
changed ||= next !== entry;
|
|
1068
|
+
return next;
|
|
1069
|
+
});
|
|
1070
|
+
return changed ? entries : schema;
|
|
1071
|
+
}
|
|
1072
|
+
if (!isRecord(schema)) return schema;
|
|
1073
|
+
let changed = false;
|
|
1074
|
+
const entries = Object.entries(schema).flatMap(([key, value]) => {
|
|
1075
|
+
if (key === "items" && schemaAllowsArrayType(schema) && isRecord(value) && isTrulyEmptySchema(value)) {
|
|
1076
|
+
changed = true;
|
|
1077
|
+
return [];
|
|
1078
|
+
}
|
|
1079
|
+
if (ARRAY_ITEMS_SCHEMA_OBJECT_KEYS.has(key)) {
|
|
1080
|
+
const next = stripEmptyArrayItemsFromArraySchemas(value);
|
|
1081
|
+
changed ||= next !== value;
|
|
1082
|
+
return [[key, next]];
|
|
1083
|
+
}
|
|
1084
|
+
if (ARRAY_ITEMS_SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(value)) {
|
|
1085
|
+
const next = stripEmptyArrayItemsFromArraySchemas(value);
|
|
1086
|
+
changed ||= next !== value;
|
|
1087
|
+
return [[key, next]];
|
|
1088
|
+
}
|
|
1089
|
+
if (ARRAY_ITEMS_SCHEMA_MAP_KEYS.has(key) && isRecord(value)) {
|
|
1090
|
+
let mapChanged = false;
|
|
1091
|
+
const next = Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => {
|
|
1092
|
+
const entryNext = stripEmptyArrayItemsFromArraySchemas(entryValue);
|
|
1093
|
+
mapChanged ||= entryNext !== entryValue;
|
|
1094
|
+
return [entryKey, entryNext];
|
|
1095
|
+
}));
|
|
1096
|
+
changed ||= mapChanged;
|
|
1097
|
+
return [[key, mapChanged ? next : value]];
|
|
1098
|
+
}
|
|
1099
|
+
return [[key, value]];
|
|
1100
|
+
});
|
|
1101
|
+
return changed ? Object.fromEntries(entries) : schema;
|
|
1102
|
+
}
|
|
1103
|
+
function copySchemaMeta(from, to) {
|
|
1104
|
+
for (const key of [
|
|
1105
|
+
"title",
|
|
1106
|
+
"description",
|
|
1107
|
+
"default"
|
|
1108
|
+
]) if (key in from && from[key] !== void 0) to[key] = from[key];
|
|
1109
|
+
}
|
|
1110
|
+
function extendSchemaDefs(defs, schema) {
|
|
1111
|
+
const defsEntry = schema.$defs && typeof schema.$defs === "object" && !Array.isArray(schema.$defs) ? schema.$defs : void 0;
|
|
1112
|
+
const legacyDefsEntry = schema.definitions && typeof schema.definitions === "object" && !Array.isArray(schema.definitions) ? schema.definitions : void 0;
|
|
1113
|
+
if (!defsEntry && !legacyDefsEntry) return defs;
|
|
1114
|
+
const next = defs ? {
|
|
1115
|
+
$defs: new Map(defs.$defs),
|
|
1116
|
+
definitions: new Map(defs.definitions)
|
|
1117
|
+
} : {
|
|
1118
|
+
$defs: /* @__PURE__ */ new Map(),
|
|
1119
|
+
definitions: /* @__PURE__ */ new Map()
|
|
1120
|
+
};
|
|
1121
|
+
if (defsEntry) for (const [key, value] of Object.entries(defsEntry)) next.$defs.set(key, value);
|
|
1122
|
+
if (legacyDefsEntry) for (const [key, value] of Object.entries(legacyDefsEntry)) next.definitions.set(key, value);
|
|
1123
|
+
return next;
|
|
1124
|
+
}
|
|
1125
|
+
function decodeJsonPointerSegment(segment) {
|
|
1126
|
+
return segment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
1127
|
+
}
|
|
1128
|
+
function resolveJsonPointerPath(value, segments) {
|
|
1129
|
+
let current = value;
|
|
1130
|
+
for (const segment of segments) {
|
|
1131
|
+
if (!current || typeof current !== "object") return;
|
|
1132
|
+
const key = decodeJsonPointerSegment(segment);
|
|
1133
|
+
if (Array.isArray(current)) {
|
|
1134
|
+
const index = /^(?:0|[1-9]\d*)$/.test(key) ? Number(key) : -1;
|
|
1135
|
+
if (index < 0 || index >= current.length) return;
|
|
1136
|
+
current = current[index];
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
const record = current;
|
|
1140
|
+
if (!Object.hasOwn(record, key)) return;
|
|
1141
|
+
current = record[key];
|
|
1142
|
+
}
|
|
1143
|
+
return current;
|
|
1144
|
+
}
|
|
1145
|
+
function resolveLocalJsonPointer(rootDocument, ref) {
|
|
1146
|
+
if (!ref.startsWith("#/")) return;
|
|
1147
|
+
return resolveJsonPointerPath(rootDocument, ref.slice(2).split("/"));
|
|
1148
|
+
}
|
|
1149
|
+
const SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set([
|
|
1150
|
+
"$defs",
|
|
1151
|
+
"definitions",
|
|
1152
|
+
"dependentSchemas",
|
|
1153
|
+
"patternProperties",
|
|
1154
|
+
"properties"
|
|
1155
|
+
]);
|
|
1156
|
+
const SCHEMA_OBJECT_KEYS = /* @__PURE__ */ new Set([
|
|
1157
|
+
"additionalProperties",
|
|
1158
|
+
"contains",
|
|
1159
|
+
"else",
|
|
1160
|
+
"if",
|
|
1161
|
+
"items",
|
|
1162
|
+
"not",
|
|
1163
|
+
"propertyNames",
|
|
1164
|
+
"then"
|
|
1165
|
+
]);
|
|
1166
|
+
const SCHEMA_ARRAY_KEYS = /* @__PURE__ */ new Set([
|
|
1167
|
+
"allOf",
|
|
1168
|
+
"anyOf",
|
|
1169
|
+
"items",
|
|
1170
|
+
"oneOf",
|
|
1171
|
+
"prefixItems"
|
|
1172
|
+
]);
|
|
1173
|
+
const SCHEMA_LITERAL_KEYS = /* @__PURE__ */ new Set([
|
|
1174
|
+
"const",
|
|
1175
|
+
"default",
|
|
1176
|
+
"enum",
|
|
1177
|
+
"examples"
|
|
1178
|
+
]);
|
|
1179
|
+
function tryResolveLocalRef(ref, defs, rootDocument) {
|
|
1180
|
+
const match = ref.match(/^#\/(\$defs|definitions)\/([^/]+)(?:\/(.*))?$/);
|
|
1181
|
+
if (match && defs) {
|
|
1182
|
+
const namespace = match[1] === "$defs" ? defs.$defs : defs.definitions;
|
|
1183
|
+
const name = decodeJsonPointerSegment(match[2] ?? "");
|
|
1184
|
+
const resolved = name ? namespace.get(name) : void 0;
|
|
1185
|
+
if (resolved !== void 0) return resolveJsonPointerPath(resolved, match[3] ? match[3].split("/") : []);
|
|
1186
|
+
}
|
|
1187
|
+
return resolveLocalJsonPointer(rootDocument, ref);
|
|
1188
|
+
}
|
|
1189
|
+
function inlineLocalSchemaRefsWithDefs(schema, defs, refStack, state, rootDocument) {
|
|
1190
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
1191
|
+
if (Array.isArray(schema)) return schema.map((entry) => inlineLocalSchemaRefsWithDefs(entry, defs, refStack, state, rootDocument));
|
|
1192
|
+
const obj = schema;
|
|
1193
|
+
const nextDefs = extendSchemaDefs(defs, obj);
|
|
1194
|
+
const refValue = typeof obj.$ref === "string" ? obj.$ref : void 0;
|
|
1195
|
+
if (refValue) {
|
|
1196
|
+
if (refStack?.has(refValue)) return {};
|
|
1197
|
+
const resolved = tryResolveLocalRef(refValue, nextDefs, rootDocument);
|
|
1198
|
+
if (resolved === void 0) {
|
|
1199
|
+
if (refValue.startsWith("#/")) state.unresolvedLocalRefs = true;
|
|
1200
|
+
return { ...obj };
|
|
1201
|
+
}
|
|
1202
|
+
const nextRefStack = refStack ? new Set(refStack) : /* @__PURE__ */ new Set();
|
|
1203
|
+
nextRefStack.add(refValue);
|
|
1204
|
+
const inlined = inlineLocalSchemaRefsWithDefs(resolved, nextDefs, nextRefStack, state, rootDocument);
|
|
1205
|
+
if (!inlined || typeof inlined !== "object" || Array.isArray(inlined)) return inlined;
|
|
1206
|
+
const result = { ...inlined };
|
|
1207
|
+
copySchemaMeta(obj, result);
|
|
1208
|
+
if (obj.nullable === true) result.nullable = true;
|
|
1209
|
+
return result;
|
|
1210
|
+
}
|
|
1211
|
+
const result = {};
|
|
1212
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1213
|
+
if (key === "$defs" || key === "definitions" || key === "components") continue;
|
|
1214
|
+
if (SCHEMA_LITERAL_KEYS.has(key)) {
|
|
1215
|
+
setOwnSchemaProperty(result, key, value);
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
if (SCHEMA_MAP_KEYS.has(key) && isRecord(value)) {
|
|
1219
|
+
setOwnSchemaProperty(result, key, Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [entryKey, inlineLocalSchemaRefsWithDefs(entryValue, nextDefs, refStack, state, rootDocument)])));
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
if (SCHEMA_OBJECT_KEYS.has(key) && isRecord(value)) {
|
|
1223
|
+
setOwnSchemaProperty(result, key, inlineLocalSchemaRefsWithDefs(value, nextDefs, refStack, state, rootDocument));
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(value)) {
|
|
1227
|
+
setOwnSchemaProperty(result, key, value.map((entry) => inlineLocalSchemaRefsWithDefs(entry, nextDefs, refStack, state, rootDocument)));
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
setOwnSchemaProperty(result, key, value);
|
|
1231
|
+
}
|
|
1232
|
+
if (state.unresolvedLocalRefs) {
|
|
1233
|
+
if ("$defs" in obj) result.$defs = obj.$defs;
|
|
1234
|
+
if ("definitions" in obj) result.definitions = obj.definitions;
|
|
1235
|
+
if ("components" in obj) result.components = obj.components;
|
|
1236
|
+
}
|
|
1237
|
+
return result;
|
|
1238
|
+
}
|
|
1239
|
+
/** Inline local $ref pointers so providers receive self-contained tool schemas. */
|
|
1240
|
+
function inlineLocalToolSchemaRefs(schema) {
|
|
1241
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
1242
|
+
return inlineLocalSchemaRefsWithDefs(schema, extendSchemaDefs(void 0, schema), void 0, { unresolvedLocalRefs: false }, schema);
|
|
1243
|
+
}
|
|
1244
|
+
const OPENAPI_SCHEMA_ANNOTATION_KEYS = /* @__PURE__ */ new Set([
|
|
1245
|
+
"discriminator",
|
|
1246
|
+
"externalDocs",
|
|
1247
|
+
"readOnly",
|
|
1248
|
+
"writeOnly",
|
|
1249
|
+
"xml",
|
|
1250
|
+
"example"
|
|
1251
|
+
]);
|
|
1252
|
+
function appendNullSchemaType(type) {
|
|
1253
|
+
if (type === "null") return type;
|
|
1254
|
+
if (typeof type === "string") return [type, "null"];
|
|
1255
|
+
if (Array.isArray(type)) return type.includes("null") ? type : [...type, "null"];
|
|
1256
|
+
return type;
|
|
1257
|
+
}
|
|
1258
|
+
function isNullSchemaLike(schema) {
|
|
1259
|
+
if (!isRecord(schema)) return false;
|
|
1260
|
+
if (schema.type === "null") return true;
|
|
1261
|
+
if (Array.isArray(schema.type) && schema.type.includes("null")) return true;
|
|
1262
|
+
if ("const" in schema && schema.const === null) return true;
|
|
1263
|
+
return Array.isArray(schema.enum) && schema.enum.includes(null);
|
|
1264
|
+
}
|
|
1265
|
+
function hasOpenApiComposition(schema) {
|
|
1266
|
+
return [
|
|
1267
|
+
"allOf",
|
|
1268
|
+
"anyOf",
|
|
1269
|
+
"oneOf"
|
|
1270
|
+
].some((key) => Array.isArray(schema[key]));
|
|
1271
|
+
}
|
|
1272
|
+
function schemaCompositionAlreadyAllowsNull(schema) {
|
|
1273
|
+
return Array.isArray(schema.anyOf) && schema.anyOf.some(isNullSchemaLike) || Array.isArray(schema.oneOf) && schema.oneOf.some(isNullSchemaLike);
|
|
1274
|
+
}
|
|
1275
|
+
function wrapNullableComposedSchema(schema) {
|
|
1276
|
+
if (schemaCompositionAlreadyAllowsNull(schema)) return schema;
|
|
1277
|
+
const wrapped = { anyOf: [schema, { type: "null" }] };
|
|
1278
|
+
copySchemaMeta(schema, wrapped);
|
|
1279
|
+
return wrapped;
|
|
1280
|
+
}
|
|
1281
|
+
function normalizeOpenApiSchemaKeywords(schema) {
|
|
1282
|
+
if (Array.isArray(schema)) {
|
|
1283
|
+
let changed = false;
|
|
1284
|
+
const normalized = schema.map((entry) => {
|
|
1285
|
+
const next = normalizeOpenApiSchemaKeywords(entry);
|
|
1286
|
+
changed ||= next !== entry;
|
|
1287
|
+
return next;
|
|
1288
|
+
});
|
|
1289
|
+
return changed ? normalized : schema;
|
|
1290
|
+
}
|
|
1291
|
+
if (!isRecord(schema)) return schema;
|
|
1292
|
+
let changed = false;
|
|
1293
|
+
const nullable = schema.nullable === true;
|
|
1294
|
+
const normalized = {};
|
|
1295
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
1296
|
+
if (key === "nullable" || OPENAPI_SCHEMA_ANNOTATION_KEYS.has(key)) {
|
|
1297
|
+
changed = true;
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
if (SCHEMA_LITERAL_KEYS.has(key)) {
|
|
1301
|
+
normalized[key] = value;
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
if (SCHEMA_MAP_KEYS.has(key) && isRecord(value)) {
|
|
1305
|
+
let mapChanged = false;
|
|
1306
|
+
const next = Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => {
|
|
1307
|
+
const nextEntry = normalizeOpenApiSchemaKeywords(entryValue);
|
|
1308
|
+
mapChanged ||= nextEntry !== entryValue;
|
|
1309
|
+
return [entryKey, nextEntry];
|
|
1310
|
+
}));
|
|
1311
|
+
normalized[key] = mapChanged ? next : value;
|
|
1312
|
+
changed ||= mapChanged;
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
if (key === "components") {
|
|
1316
|
+
normalized[key] = value;
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
if (SCHEMA_OBJECT_KEYS.has(key) && isRecord(value)) {
|
|
1320
|
+
const next = normalizeOpenApiSchemaKeywords(value);
|
|
1321
|
+
normalized[key] = next;
|
|
1322
|
+
changed ||= next !== value;
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(value)) {
|
|
1326
|
+
const next = value.map(normalizeOpenApiSchemaKeywords);
|
|
1327
|
+
normalized[key] = next;
|
|
1328
|
+
changed ||= next.some((entry, index) => entry !== value[index]);
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
normalized[key] = value;
|
|
1332
|
+
}
|
|
1333
|
+
if (nullable) {
|
|
1334
|
+
if (hasOpenApiComposition(normalized)) return wrapNullableComposedSchema(normalized);
|
|
1335
|
+
if ("type" in normalized) {
|
|
1336
|
+
const nextType = appendNullSchemaType(normalized.type);
|
|
1337
|
+
if (nextType !== normalized.type) normalized.type = nextType;
|
|
1338
|
+
}
|
|
1339
|
+
if (Array.isArray(normalized.enum) && !normalized.enum.includes(null)) normalized.enum = [...normalized.enum, null];
|
|
1340
|
+
}
|
|
1341
|
+
return changed || nullable ? normalized : schema;
|
|
1342
|
+
}
|
|
1343
|
+
function normalizeToolParameterSchemaUncached(schema, options) {
|
|
1344
|
+
const inlinedSchema = normalizeOpenApiSchemaKeywords(inlineLocalToolSchemaRefs(schema));
|
|
1345
|
+
const schemaRecord = inlinedSchema && typeof inlinedSchema === "object" ? inlinedSchema : void 0;
|
|
1346
|
+
if (!schemaRecord) return inlinedSchema;
|
|
1347
|
+
const normalizedProvider = normalizeLowercaseStringOrEmpty(options?.modelProvider);
|
|
1348
|
+
const normalizedModelId = normalizeLowercaseStringOrEmpty(options?.modelId);
|
|
1349
|
+
const normalizedToolSchemaProfile = normalizeLowercaseStringOrEmpty(options?.modelCompat?.toolSchemaProfile);
|
|
1350
|
+
const isGeminiProvider = normalizedProvider.includes("google") || normalizedProvider.includes("gemini") || isGeminiModelId(normalizedModelId) || normalizedToolSchemaProfile === "gemini";
|
|
1351
|
+
const isAnthropicProvider = normalizedProvider.includes("anthropic");
|
|
1352
|
+
const unsupportedToolSchemaKeywords = resolveUnsupportedToolSchemaKeywords(options?.modelCompat);
|
|
1353
|
+
const omitEmptyArrayItems = shouldOmitEmptyArrayItems(options?.modelCompat);
|
|
1354
|
+
const isLlamacppGbnfProfile = normalizedToolSchemaProfile === "llamacpp";
|
|
1355
|
+
function applyProviderCleaning(s) {
|
|
1356
|
+
const normalizedSchema = normalizeArraySchemasMissingItems(s);
|
|
1357
|
+
let arrayItemsCompatibleSchema = omitEmptyArrayItems ? stripEmptyArrayItemsFromArraySchemas(normalizedSchema) : normalizedSchema;
|
|
1358
|
+
if (isLlamacppGbnfProfile) arrayItemsCompatibleSchema = cleanSchemaForLlamacppGbnf(arrayItemsCompatibleSchema);
|
|
1359
|
+
if (isGeminiProvider && !isAnthropicProvider) {
|
|
1360
|
+
const geminiCompatibleSchema = cleanSchemaForGemini(arrayItemsCompatibleSchema);
|
|
1361
|
+
return unsupportedToolSchemaKeywords.size > 0 ? stripUnsupportedSchemaKeywords(geminiCompatibleSchema, unsupportedToolSchemaKeywords) : geminiCompatibleSchema;
|
|
1362
|
+
}
|
|
1363
|
+
if (unsupportedToolSchemaKeywords.size > 0) return stripUnsupportedSchemaKeywords(arrayItemsCompatibleSchema, unsupportedToolSchemaKeywords);
|
|
1364
|
+
return arrayItemsCompatibleSchema;
|
|
1365
|
+
}
|
|
1366
|
+
const conditionalKey = getTopLevelConditionalKey(schemaRecord);
|
|
1367
|
+
const flattenableVariantKey = getFlattenableVariantKey(schemaRecord);
|
|
1368
|
+
if (hasTopLevelObjectSchema(schemaRecord, conditionalKey)) return applyProviderCleaning(schemaRecord);
|
|
1369
|
+
if (isObjectLikeSchemaMissingType(schemaRecord, conditionalKey)) return applyProviderCleaning({
|
|
1370
|
+
...schemaRecord,
|
|
1371
|
+
type: "object",
|
|
1372
|
+
properties: isRecord(schemaRecord.properties) ? schemaRecord.properties : {}
|
|
1373
|
+
});
|
|
1374
|
+
if (isTypedObjectSchemaMissingValidProperties(schemaRecord, conditionalKey)) return applyProviderCleaning({
|
|
1375
|
+
...schemaRecord,
|
|
1376
|
+
properties: {}
|
|
1377
|
+
});
|
|
1378
|
+
if (!flattenableVariantKey) {
|
|
1379
|
+
if (isTrulyEmptySchema(schemaRecord)) return applyProviderCleaning({
|
|
1380
|
+
type: "object",
|
|
1381
|
+
properties: {}
|
|
1382
|
+
});
|
|
1383
|
+
if (conditionalKey === "allOf") return applyProviderCleaning(inlinedSchema);
|
|
1384
|
+
return applyProviderCleaning(inlinedSchema);
|
|
1385
|
+
}
|
|
1386
|
+
const variants = schemaRecord[flattenableVariantKey];
|
|
1387
|
+
const mergedProperties = isRecord(schemaRecord.properties) ? { ...schemaRecord.properties } : {};
|
|
1388
|
+
const requiredCounts = /* @__PURE__ */ new Map();
|
|
1389
|
+
let objectVariants = 0;
|
|
1390
|
+
for (const entry of variants) {
|
|
1391
|
+
if (!entry || typeof entry !== "object") continue;
|
|
1392
|
+
const props = entry.properties;
|
|
1393
|
+
if (!props || typeof props !== "object") continue;
|
|
1394
|
+
objectVariants += 1;
|
|
1395
|
+
for (const [key, value] of Object.entries(props)) {
|
|
1396
|
+
if (!(key in mergedProperties)) {
|
|
1397
|
+
mergedProperties[key] = value;
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
mergedProperties[key] = mergePropertySchemas(mergedProperties[key], value);
|
|
1401
|
+
}
|
|
1402
|
+
const required = Array.isArray(entry.required) ? entry.required : [];
|
|
1403
|
+
for (const key of required) {
|
|
1404
|
+
if (typeof key !== "string") continue;
|
|
1405
|
+
requiredCounts.set(key, (requiredCounts.get(key) ?? 0) + 1);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
const baseRequired = Array.isArray(schemaRecord.required) ? schemaRecord.required.filter((key) => typeof key === "string") : void 0;
|
|
1409
|
+
const mergedRequired = baseRequired && baseRequired.length > 0 ? baseRequired : objectVariants > 0 ? Array.from(requiredCounts.entries()).filter(([, count]) => count === objectVariants).map(([key]) => key) : void 0;
|
|
1410
|
+
const nextSchema = { ...schemaRecord };
|
|
1411
|
+
return applyProviderCleaning({
|
|
1412
|
+
type: "object",
|
|
1413
|
+
...typeof nextSchema.title === "string" ? { title: nextSchema.title } : {},
|
|
1414
|
+
...typeof nextSchema.description === "string" ? { description: nextSchema.description } : {},
|
|
1415
|
+
properties: Object.keys(mergedProperties).length > 0 ? mergedProperties : schemaRecord.properties ?? {},
|
|
1416
|
+
...mergedRequired && mergedRequired.length > 0 ? { required: mergedRequired } : {},
|
|
1417
|
+
additionalProperties: "additionalProperties" in schemaRecord ? schemaRecord.additionalProperties : true
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
/** Return a provider-compatible JSON schema for a model-facing tool. */
|
|
1421
|
+
function normalizeToolParameterSchema(schema, options) {
|
|
1422
|
+
if (!schema || typeof schema !== "object") return normalizeToolParameterSchemaUncached(schema, options);
|
|
1423
|
+
const cacheKey = resolveToolParameterSchemaCacheKey(options);
|
|
1424
|
+
const cached = getCachedToolParameterSchema(schema, cacheKey);
|
|
1425
|
+
if (cached) return cached;
|
|
1426
|
+
return rememberCachedToolParameterSchema(schema, cacheKey, normalizeToolParameterSchemaUncached(schema, options));
|
|
1427
|
+
}
|
|
1428
|
+
//#endregion
|
|
1429
|
+
//#region packages/ai/src/providers/openai-tool-schema-compat.ts
|
|
1430
|
+
const OPENAI_STRICT_COMPAT_SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set([
|
|
1431
|
+
"$defs",
|
|
1432
|
+
"definitions",
|
|
1433
|
+
"dependentSchemas",
|
|
1434
|
+
"dependencies",
|
|
1435
|
+
"patternProperties",
|
|
1436
|
+
"properties"
|
|
1437
|
+
]);
|
|
1438
|
+
const OPENAI_NULLABLE_ANNOTATION_KEYS = /* @__PURE__ */ new Set([
|
|
1439
|
+
"default",
|
|
1440
|
+
"description",
|
|
1441
|
+
"examples",
|
|
1442
|
+
"format",
|
|
1443
|
+
"title"
|
|
1444
|
+
]);
|
|
1445
|
+
const OPENAI_STRICT_COMPAT_SCHEMA_NESTED_KEYS = /* @__PURE__ */ new Set([
|
|
1446
|
+
"additionalItems",
|
|
1447
|
+
"additionalProperties",
|
|
1448
|
+
"allOf",
|
|
1449
|
+
"anyOf",
|
|
1450
|
+
"contains",
|
|
1451
|
+
"contentSchema",
|
|
1452
|
+
"else",
|
|
1453
|
+
"if",
|
|
1454
|
+
"items",
|
|
1455
|
+
"not",
|
|
1456
|
+
"oneOf",
|
|
1457
|
+
"prefixItems",
|
|
1458
|
+
"propertyNames",
|
|
1459
|
+
"then",
|
|
1460
|
+
"unevaluatedItems",
|
|
1461
|
+
"unevaluatedProperties"
|
|
1462
|
+
]);
|
|
1463
|
+
function normalizeOpenAIStrictCompatSchemaMap(schema) {
|
|
1464
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return schema;
|
|
1465
|
+
let changed = false;
|
|
1466
|
+
const normalized = {};
|
|
1467
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
1468
|
+
const next = normalizeOpenAIStrictCompatSchemaRecursive(value, { promoteEmptyObject: false });
|
|
1469
|
+
normalized[key] = next;
|
|
1470
|
+
changed ||= next !== value;
|
|
1471
|
+
}
|
|
1472
|
+
return changed ? normalized : schema;
|
|
1473
|
+
}
|
|
1474
|
+
function normalizeOpenAIStrictCompatSchemaRecursive(schema, options) {
|
|
1475
|
+
if (Array.isArray(schema)) {
|
|
1476
|
+
let changed = false;
|
|
1477
|
+
const normalized = schema.map((entry) => {
|
|
1478
|
+
const next = normalizeOpenAIStrictCompatSchemaRecursive(entry, { promoteEmptyObject: false });
|
|
1479
|
+
changed ||= next !== entry;
|
|
1480
|
+
return next;
|
|
1481
|
+
});
|
|
1482
|
+
return changed ? normalized : schema;
|
|
1483
|
+
}
|
|
1484
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
1485
|
+
const record = schema;
|
|
1486
|
+
let changed = false;
|
|
1487
|
+
let hadNullType = false;
|
|
1488
|
+
const normalized = {};
|
|
1489
|
+
for (const [key, value] of Object.entries(record)) {
|
|
1490
|
+
if (value === null && OPENAI_NULLABLE_ANNOTATION_KEYS.has(key)) {
|
|
1491
|
+
changed = true;
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
if (value === null && key === "type") {
|
|
1495
|
+
hadNullType = true;
|
|
1496
|
+
changed = true;
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
const next = OPENAI_STRICT_COMPAT_SCHEMA_MAP_KEYS.has(key) ? normalizeOpenAIStrictCompatSchemaMap(value) : OPENAI_STRICT_COMPAT_SCHEMA_NESTED_KEYS.has(key) ? normalizeOpenAIStrictCompatSchemaRecursive(value, { promoteEmptyObject: false }) : value;
|
|
1500
|
+
normalized[key] = next;
|
|
1501
|
+
changed ||= next !== value;
|
|
1502
|
+
}
|
|
1503
|
+
if (Object.keys(normalized).length === 0) {
|
|
1504
|
+
if (!options.promoteEmptyObject) return schema;
|
|
1505
|
+
return {
|
|
1506
|
+
type: "object",
|
|
1507
|
+
properties: {},
|
|
1508
|
+
required: [],
|
|
1509
|
+
additionalProperties: false
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
const hasObjectShapeHints = normalized.properties && typeof normalized.properties === "object" && !Array.isArray(normalized.properties) || Array.isArray(normalized.required);
|
|
1513
|
+
const hasArrayShapeHints = "items" in normalized;
|
|
1514
|
+
if (!("type" in normalized) && hasObjectShapeHints !== hasArrayShapeHints) {
|
|
1515
|
+
normalized.type = hasObjectShapeHints ? "object" : "array";
|
|
1516
|
+
changed = true;
|
|
1517
|
+
} else if (hadNullType && !("type" in normalized)) normalized.type = null;
|
|
1518
|
+
if (normalized.type === "object" && !("properties" in normalized)) {
|
|
1519
|
+
normalized.properties = {};
|
|
1520
|
+
changed = true;
|
|
1521
|
+
}
|
|
1522
|
+
const hasEmptyProperties = normalized.properties && typeof normalized.properties === "object" && !Array.isArray(normalized.properties) && Object.keys(normalized.properties).length === 0;
|
|
1523
|
+
if (normalized.type === "object" && !Array.isArray(normalized.required) && hasEmptyProperties) {
|
|
1524
|
+
normalized.required = [];
|
|
1525
|
+
changed = true;
|
|
1526
|
+
}
|
|
1527
|
+
if (normalized.type === "object" && hasEmptyProperties && !("additionalProperties" in normalized)) {
|
|
1528
|
+
normalized.additionalProperties = false;
|
|
1529
|
+
changed = true;
|
|
1530
|
+
}
|
|
1531
|
+
return changed ? normalized : schema;
|
|
1532
|
+
}
|
|
1533
|
+
/** Repairs recoverable OpenAI tool-schema shapes before canonical normalization. */
|
|
1534
|
+
function normalizeOpenAIStrictCompatSchema(schema) {
|
|
1535
|
+
return normalizeOpenAIStrictCompatSchemaRecursive(schema, { promoteEmptyObject: true });
|
|
1536
|
+
}
|
|
1537
|
+
/** Finds schema paths that violate OpenAI strict tool-schema requirements. */
|
|
1538
|
+
function findOpenAIStrictSchemaViolations(schema, path, options) {
|
|
1539
|
+
if (Array.isArray(schema)) {
|
|
1540
|
+
if (options?.requireObjectRoot) return [`${path}.type`];
|
|
1541
|
+
return schema.flatMap((item, index) => findOpenAIStrictSchemaViolations(item, `${path}[${index}]`));
|
|
1542
|
+
}
|
|
1543
|
+
if (!schema || typeof schema !== "object") return options?.requireObjectRoot ? [`${path}.type`] : [];
|
|
1544
|
+
const record = schema;
|
|
1545
|
+
const violations = [];
|
|
1546
|
+
for (const key of [
|
|
1547
|
+
"anyOf",
|
|
1548
|
+
"oneOf",
|
|
1549
|
+
"allOf"
|
|
1550
|
+
]) if (key in record) violations.push(`${path}.${key}`);
|
|
1551
|
+
if (Array.isArray(record.type)) violations.push(`${path}.type`);
|
|
1552
|
+
const properties = record.properties && typeof record.properties === "object" && !Array.isArray(record.properties) ? record.properties : void 0;
|
|
1553
|
+
if (record.type === "object") {
|
|
1554
|
+
if (record.additionalProperties !== false) violations.push(`${path}.additionalProperties`);
|
|
1555
|
+
const required = Array.isArray(record.required) ? record.required.filter((entry) => typeof entry === "string") : void 0;
|
|
1556
|
+
if (!required) violations.push(`${path}.required`);
|
|
1557
|
+
else if (properties) {
|
|
1558
|
+
const requiredSet = new Set(required);
|
|
1559
|
+
for (const key of Object.keys(properties)) if (!requiredSet.has(key)) violations.push(`${path}.required.${key}`);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
for (const key of OPENAI_STRICT_COMPAT_SCHEMA_MAP_KEYS) {
|
|
1563
|
+
const schemaMap = record[key];
|
|
1564
|
+
if (!schemaMap || typeof schemaMap !== "object" || Array.isArray(schemaMap)) continue;
|
|
1565
|
+
for (const [entryKey, value] of Object.entries(schemaMap)) violations.push(...findOpenAIStrictSchemaViolations(value, `${path}.${key}.${entryKey}`));
|
|
1566
|
+
}
|
|
1567
|
+
for (const key of OPENAI_STRICT_COMPAT_SCHEMA_NESTED_KEYS) {
|
|
1568
|
+
const value = record[key];
|
|
1569
|
+
if (value && typeof value === "object") violations.push(...findOpenAIStrictSchemaViolations(value, `${path}.${key}`));
|
|
1570
|
+
}
|
|
1571
|
+
return violations;
|
|
1572
|
+
}
|
|
1573
|
+
//#endregion
|
|
1574
|
+
//#region packages/ai/src/providers/openai-tool-schema.ts
|
|
1575
|
+
/**
|
|
1576
|
+
* OpenAI strict JSON-schema normalization for tool inventories and request payloads.
|
|
1577
|
+
*
|
|
1578
|
+
* Caches normalized object inputs by provider compatibility so repeated inventory builds preserve identity.
|
|
1579
|
+
*/
|
|
1580
|
+
const MAX_STRICT_SCHEMA_CACHE_ENTRIES_PER_SCHEMA = 8;
|
|
1581
|
+
const strictOpenAISchemaCache = /* @__PURE__ */ new WeakMap();
|
|
1582
|
+
function resolveToolSchemaModelCompat(compat) {
|
|
1583
|
+
if (!compat) return;
|
|
1584
|
+
const unsupportedToolSchemaKeywords = Array.isArray(compat.unsupportedToolSchemaKeywords) ? compat.unsupportedToolSchemaKeywords.filter((keyword) => typeof keyword === "string") : [];
|
|
1585
|
+
if (unsupportedToolSchemaKeywords.length === 0 && compat.omitEmptyArrayItems !== true) return;
|
|
1586
|
+
return {
|
|
1587
|
+
...unsupportedToolSchemaKeywords.length > 0 ? { unsupportedToolSchemaKeywords } : {},
|
|
1588
|
+
...compat.omitEmptyArrayItems === true ? { omitEmptyArrayItems: true } : {}
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
function resolveStrictOpenAISchemaCacheKey(modelCompat) {
|
|
1592
|
+
const compat = resolveToolSchemaModelCompat(modelCompat);
|
|
1593
|
+
return JSON.stringify([[...compat?.unsupportedToolSchemaKeywords ?? []].toSorted(), shouldOmitEmptyArrayItems(compat)]);
|
|
1594
|
+
}
|
|
1595
|
+
function readCachedStrictOpenAISchema(schema, key) {
|
|
1596
|
+
return strictOpenAISchemaCache.get(schema)?.find((entry) => entry.key === key)?.value;
|
|
1597
|
+
}
|
|
1598
|
+
function rememberStrictOpenAISchema(schema, key, value) {
|
|
1599
|
+
const entries = strictOpenAISchemaCache.get(schema) ?? [];
|
|
1600
|
+
strictOpenAISchemaCache.set(schema, [{
|
|
1601
|
+
key,
|
|
1602
|
+
value
|
|
1603
|
+
}, ...entries.filter((entry) => entry.key !== key)].slice(0, MAX_STRICT_SCHEMA_CACHE_ENTRIES_PER_SCHEMA));
|
|
1604
|
+
return value;
|
|
1605
|
+
}
|
|
1606
|
+
/** Normalizes a tool parameter schema into the OpenAI strict JSON-schema subset. */
|
|
1607
|
+
function normalizeStrictOpenAIJsonSchema(schema, modelCompat) {
|
|
1608
|
+
const schemaInput = schema ?? {};
|
|
1609
|
+
if (!schemaInput || typeof schemaInput !== "object") return normalizeStrictOpenAIJsonSchemaRecursive(normalizeToolParameterSchema(schemaInput, { modelCompat: resolveToolSchemaModelCompat(modelCompat) }), 0);
|
|
1610
|
+
const cacheKey = resolveStrictOpenAISchemaCacheKey(modelCompat);
|
|
1611
|
+
const cached = readCachedStrictOpenAISchema(schemaInput, cacheKey);
|
|
1612
|
+
if (cached !== void 0) return cached;
|
|
1613
|
+
return rememberStrictOpenAISchema(schemaInput, cacheKey, normalizeStrictOpenAIJsonSchemaRecursive(normalizeToolParameterSchema(schemaInput, { modelCompat: resolveToolSchemaModelCompat(modelCompat) }), 0));
|
|
1614
|
+
}
|
|
1615
|
+
function normalizeStrictOpenAIJsonSchemaRecursive(schema, depth) {
|
|
1616
|
+
if (Array.isArray(schema)) {
|
|
1617
|
+
let changed = false;
|
|
1618
|
+
const normalized = schema.map((entry) => {
|
|
1619
|
+
const next = normalizeStrictOpenAIJsonSchemaRecursive(entry, depth);
|
|
1620
|
+
changed ||= next !== entry;
|
|
1621
|
+
return next;
|
|
1622
|
+
});
|
|
1623
|
+
return changed ? normalized : schema;
|
|
1624
|
+
}
|
|
1625
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
1626
|
+
const record = schema;
|
|
1627
|
+
let changed = false;
|
|
1628
|
+
const normalized = {};
|
|
1629
|
+
for (const [key, value] of Object.entries(record)) {
|
|
1630
|
+
const next = normalizeStrictOpenAIJsonSchemaRecursive(value, key === "properties" ? depth : depth + 1);
|
|
1631
|
+
normalized[key] = next;
|
|
1632
|
+
changed ||= next !== value;
|
|
1633
|
+
}
|
|
1634
|
+
if (normalized.type === "object") {
|
|
1635
|
+
const properties = normalized.properties && typeof normalized.properties === "object" && !Array.isArray(normalized.properties) ? normalized.properties : void 0;
|
|
1636
|
+
if (properties && Object.keys(properties).length === 0 && !Array.isArray(normalized.required)) {
|
|
1637
|
+
normalized.required = [];
|
|
1638
|
+
changed = true;
|
|
1639
|
+
}
|
|
1640
|
+
if (depth === 0 && !("additionalProperties" in normalized)) {
|
|
1641
|
+
normalized.additionalProperties = false;
|
|
1642
|
+
changed = true;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
return changed ? normalized : schema;
|
|
1646
|
+
}
|
|
1647
|
+
/** Normalizes tool parameters using strict OpenAI rules only when strict mode is active. */
|
|
1648
|
+
function normalizeOpenAIStrictToolParameters(schema, strict, modelCompat) {
|
|
1649
|
+
const toolSchemaCompat = resolveToolSchemaModelCompat(modelCompat);
|
|
1650
|
+
if (!strict) return normalizeToolParameterSchema(schema ?? {}, { modelCompat: toolSchemaCompat });
|
|
1651
|
+
return normalizeStrictOpenAIJsonSchema(schema, toolSchemaCompat);
|
|
1652
|
+
}
|
|
1653
|
+
/** Returns whether a schema already satisfies OpenAI strict tool-schema constraints. */
|
|
1654
|
+
function isStrictOpenAIJsonSchemaCompatible(schema) {
|
|
1655
|
+
return isStrictOpenAIJsonSchemaCompatibleRecursive(normalizeStrictOpenAIJsonSchema(schema));
|
|
1656
|
+
}
|
|
1657
|
+
/** Returns strict-schema diagnostics for an already materialized OpenAI tool projection. */
|
|
1658
|
+
function findOpenAIStrictToolProjectionDiagnostics(projection) {
|
|
1659
|
+
return [...projection.diagnostics.map((diagnostic) => ({
|
|
1660
|
+
toolIndex: diagnostic.toolIndex,
|
|
1661
|
+
...diagnostic.toolName ? { toolName: diagnostic.toolName } : {},
|
|
1662
|
+
violations: [...diagnostic.violations]
|
|
1663
|
+
})), ...projection.tools.flatMap((tool) => {
|
|
1664
|
+
const violations = findOpenAIStrictSchemaViolations(normalizeStrictOpenAIJsonSchema(tool.parameters), `${tool.name}.parameters`);
|
|
1665
|
+
return violations.length > 0 ? [{
|
|
1666
|
+
toolIndex: tool.toolIndex,
|
|
1667
|
+
toolName: tool.name,
|
|
1668
|
+
violations
|
|
1669
|
+
}] : [];
|
|
1670
|
+
})];
|
|
1671
|
+
}
|
|
1672
|
+
function isStrictOpenAIJsonSchemaCompatibleRecursive(schema) {
|
|
1673
|
+
if (Array.isArray(schema)) return schema.every((entry) => isStrictOpenAIJsonSchemaCompatibleRecursive(entry));
|
|
1674
|
+
if (!schema || typeof schema !== "object") return true;
|
|
1675
|
+
const record = schema;
|
|
1676
|
+
if ("anyOf" in record || "oneOf" in record || "allOf" in record) return false;
|
|
1677
|
+
if (Array.isArray(record.type)) return false;
|
|
1678
|
+
if (record.type === "object" && record.additionalProperties !== false) return false;
|
|
1679
|
+
if (record.type === "object") {
|
|
1680
|
+
const properties = record.properties && typeof record.properties === "object" && !Array.isArray(record.properties) ? record.properties : {};
|
|
1681
|
+
const required = Array.isArray(record.required) ? record.required.filter((entry) => typeof entry === "string") : void 0;
|
|
1682
|
+
if (!required) return false;
|
|
1683
|
+
const requiredSet = new Set(required);
|
|
1684
|
+
if (Object.keys(properties).some((key) => !requiredSet.has(key))) return false;
|
|
1685
|
+
}
|
|
1686
|
+
return Object.entries(record).every(([key, entry]) => {
|
|
1687
|
+
if (key === "properties" && entry && typeof entry === "object" && !Array.isArray(entry)) return Object.values(entry).every((value) => isStrictOpenAIJsonSchemaCompatibleRecursive(value));
|
|
1688
|
+
return isStrictOpenAIJsonSchemaCompatibleRecursive(entry);
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
/** Resolves strict mode for the projected tools that will be emitted in the request payload. */
|
|
1692
|
+
function resolveOpenAIProjectedToolsStrictToolFlag(projection, strict) {
|
|
1693
|
+
if (strict !== true) return strict === false ? false : void 0;
|
|
1694
|
+
return projection.tools.every((tool) => isStrictOpenAIJsonSchemaCompatible(tool.parameters));
|
|
1695
|
+
}
|
|
1696
|
+
//#endregion
|
|
1697
|
+
export { readOpenAICompletionsReasoningBatch as A, createOpenAIProviderAcceptanceHook as C, measureUtf8AppendBytes as D, log as E, resolvePromptCacheKey as M, throwIfModelStreamAborted as N, parseOpenAICompletionsUsage as O, createModelStreamCooperativeScheduler as S, isOpenAICompletionsThinkingEnabled as T, cleanSchemaForGemini as _, resolveOpenAIProjectedToolsStrictToolFlag as a, reconcileOpenAIResponsesToolChoice as b, extractToolSchemaModelCompat as c, shouldOmitEmptyArrayItems as d, stripUnsupportedSchemaKeywords as f, GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS as g, findLlamacppGbnfSchemaViolations as h, normalizeStrictOpenAIJsonSchema as i, resolveOpenAIClientBaseUrl as j, readOpenAICompletionsContentDeltas as k, normalizeToolParameterSchema as l, cleanSchemaForLlamacppGbnf as m, isStrictOpenAIJsonSchemaCompatible as n, findOpenAIStrictSchemaViolations as o, LLAMACPP_GBNF_MAX_REPETITION_THRESHOLD as p, normalizeOpenAIStrictToolParameters as r, normalizeOpenAIStrictCompatSchema as s, findOpenAIStrictToolProjectionDiagnostics as t, resolveUnsupportedToolSchemaKeywords as u, projectOpenAITools as v, createOpenAIResponseHook as w, GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP as x, reconcileOpenAICompletionsToolChoice as y };
|