@x12i/ai-dispatcher 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -5
- package/dist/index.cjs +432 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +426 -122
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -10,6 +10,318 @@ import {
|
|
|
10
10
|
// src/create-dispatcher.ts
|
|
11
11
|
import { createMetadataPacker } from "@x12i/provider-metadata";
|
|
12
12
|
|
|
13
|
+
// src/ai-input.ts
|
|
14
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
15
|
+
import {
|
|
16
|
+
createCoreClient,
|
|
17
|
+
isAiInputError,
|
|
18
|
+
resolveSettings
|
|
19
|
+
} from "@x12i/ai-input/core";
|
|
20
|
+
|
|
21
|
+
// src/types/providers.ts
|
|
22
|
+
var IMPLEMENTED_AI_PROVIDERS = ["openrouter", "bedrock", "openai", "cloudflare"];
|
|
23
|
+
function isImplementedAiProvider(provider) {
|
|
24
|
+
return IMPLEMENTED_AI_PROVIDERS.includes(provider);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/errors.ts
|
|
28
|
+
var AiDispatcherError = class extends Error {
|
|
29
|
+
constructor(code, message, details) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.details = details;
|
|
33
|
+
this.name = "AiDispatcherError";
|
|
34
|
+
}
|
|
35
|
+
code;
|
|
36
|
+
details;
|
|
37
|
+
};
|
|
38
|
+
function providerNotImplemented(provider) {
|
|
39
|
+
const implemented = [...IMPLEMENTED_AI_PROVIDERS];
|
|
40
|
+
return new AiDispatcherError(
|
|
41
|
+
"PROVIDER_NOT_IMPLEMENTED",
|
|
42
|
+
`AI provider "${provider}" is not implemented yet. Supported by @x12i/ai-dispatcher: ${implemented.map((id) => `"${id}"`).join(", ")}.`,
|
|
43
|
+
{
|
|
44
|
+
provider,
|
|
45
|
+
implementedProviders: implemented,
|
|
46
|
+
operatorHint: `Pass provider: "${implemented[0]}" (or omit provider to use the default).`
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/ai-input.ts
|
|
52
|
+
var GPT_ENCODINGS = /* @__PURE__ */ new Set(["o200k_base", "cl100k_base", "p50k_base", "p50k_edit", "r50k_base"]);
|
|
53
|
+
var inputStorage = new AsyncLocalStorage();
|
|
54
|
+
function mergeAiInput(factory, request) {
|
|
55
|
+
if (request === false) return false;
|
|
56
|
+
if (request === void 0) {
|
|
57
|
+
if (factory === false) return false;
|
|
58
|
+
return factory ?? {};
|
|
59
|
+
}
|
|
60
|
+
const base = typeof factory === "object" ? factory : {};
|
|
61
|
+
const { approvals: requestApprovals, ...requestRest } = request;
|
|
62
|
+
return {
|
|
63
|
+
...base,
|
|
64
|
+
...requestRest,
|
|
65
|
+
...requestApprovals !== void 0 ? { approvals: requestApprovals } : base.approvals !== void 0 ? { approvals: base.approvals } : {}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function visibleInputResolution(track) {
|
|
69
|
+
if (!track.enabled) return { enabled: false, changed: false };
|
|
70
|
+
const baseline = isBaselineCodec(track.codec) && track.representation === "value";
|
|
71
|
+
if (!track.changed && baseline && !track.surfaced) return void 0;
|
|
72
|
+
return {
|
|
73
|
+
enabled: true,
|
|
74
|
+
...track.model ? { model: track.model } : {},
|
|
75
|
+
mode: track.mode,
|
|
76
|
+
codec: track.codec,
|
|
77
|
+
representation: track.representation,
|
|
78
|
+
approvalSource: track.approvalSource,
|
|
79
|
+
changed: track.changed
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function wrapToolRegistry(tools) {
|
|
83
|
+
const wrapped = {};
|
|
84
|
+
for (const [name, executor] of Object.entries(tools)) {
|
|
85
|
+
if (!executor) continue;
|
|
86
|
+
wrapped[name] = wrapProviderExecutor(executor);
|
|
87
|
+
}
|
|
88
|
+
return wrapped;
|
|
89
|
+
}
|
|
90
|
+
function wrapProviderExecutor(executor) {
|
|
91
|
+
return async (args, context) => {
|
|
92
|
+
const result = await executor(args, context);
|
|
93
|
+
const track = inputStorage.getStore();
|
|
94
|
+
if (!track?.enabled || !track.client) return result;
|
|
95
|
+
return applyEncodedResult(track, result);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function applyAiInput(params) {
|
|
99
|
+
const requestSetting = params.request.aiInput;
|
|
100
|
+
delete params.request.aiInput;
|
|
101
|
+
const settings = mergeAiInput(params.factory, requestSetting);
|
|
102
|
+
if (settings === false) return disabledTrack();
|
|
103
|
+
const track = await openTrack(settings, lookupModel(settings.model, params.effectiveModel));
|
|
104
|
+
await encodeRequestText(params.request, track);
|
|
105
|
+
wrapRequestTools(params.request, track);
|
|
106
|
+
return track;
|
|
107
|
+
}
|
|
108
|
+
function disabledTrack() {
|
|
109
|
+
return {
|
|
110
|
+
enabled: false,
|
|
111
|
+
mode: "compact",
|
|
112
|
+
codec: "text",
|
|
113
|
+
representation: "value",
|
|
114
|
+
approvalSource: "baseline",
|
|
115
|
+
changed: false,
|
|
116
|
+
surfaced: false
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function openTrack(settings, model) {
|
|
120
|
+
const tokenizer = settings.tokenizer ?? "none";
|
|
121
|
+
const mode = settings.mode ?? "compact";
|
|
122
|
+
let tokenCounter;
|
|
123
|
+
try {
|
|
124
|
+
tokenCounter = await loadTokenizer(tokenizer);
|
|
125
|
+
const framing = framingFor(settings);
|
|
126
|
+
const resolved = resolveSettings({
|
|
127
|
+
files: [],
|
|
128
|
+
env: {},
|
|
129
|
+
factory: {
|
|
130
|
+
mode,
|
|
131
|
+
codec: settings.codec ?? "auto",
|
|
132
|
+
readableFormat: settings.readableFormat ?? "auto",
|
|
133
|
+
sharing: settings.sharing ?? "auto",
|
|
134
|
+
tokenizer: tokenCounter?.id ?? "none",
|
|
135
|
+
framing,
|
|
136
|
+
...model ? { model } : {}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const approvals = settings.approvals;
|
|
140
|
+
const client = createCoreClient({
|
|
141
|
+
config: resolved.config,
|
|
142
|
+
explanation: resolved.explanation,
|
|
143
|
+
...approvals && approvals.length > 0 ? { policy: { approvals: [...approvals] } } : {},
|
|
144
|
+
...tokenCounter ? { tokenCounter } : {},
|
|
145
|
+
warn: () => void 0
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
enabled: true,
|
|
149
|
+
client,
|
|
150
|
+
...model ? { model } : {},
|
|
151
|
+
mode,
|
|
152
|
+
codec: "text",
|
|
153
|
+
representation: "value",
|
|
154
|
+
approvalSource: "baseline",
|
|
155
|
+
changed: false,
|
|
156
|
+
surfaced: false
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
throw mapAiInputError(error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function lookupModel(override, effectiveModel) {
|
|
163
|
+
const explicit = override?.trim();
|
|
164
|
+
const chosen = explicit ? explicit : effectiveModel.trim();
|
|
165
|
+
return chosen.length > 0 ? chosen : void 0;
|
|
166
|
+
}
|
|
167
|
+
function framingFor(settings) {
|
|
168
|
+
if (settings.framing) return settings.framing;
|
|
169
|
+
const tokenizer = settings.tokenizer ?? "none";
|
|
170
|
+
if (tokenizer !== "none" && (settings.approvals?.length ?? 0) > 0) return "self-describing";
|
|
171
|
+
return "raw";
|
|
172
|
+
}
|
|
173
|
+
async function loadTokenizer(tokenizer) {
|
|
174
|
+
if (tokenizer === "none") return void 0;
|
|
175
|
+
if (!tokenizer.startsWith("gpt:")) {
|
|
176
|
+
throw new AiDispatcherError("AI_INPUT_FAILED", `Unsupported tokenizer "${tokenizer}".`, {
|
|
177
|
+
code: "INVALID_CONFIG"
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const encoding = tokenizer.slice("gpt:".length);
|
|
181
|
+
if (!GPT_ENCODINGS.has(encoding)) {
|
|
182
|
+
throw new AiDispatcherError("AI_INPUT_FAILED", `Unsupported tokenizer "${tokenizer}".`, {
|
|
183
|
+
code: "INVALID_CONFIG"
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const gpt = await import("@x12i/ai-input/tokenizers/gpt");
|
|
187
|
+
return gpt.createGptTokenCounter(encoding);
|
|
188
|
+
}
|
|
189
|
+
async function encodeRequestText(request, track) {
|
|
190
|
+
if (typeof request.prompt === "string") request.prompt = await encodeString(track, request.prompt);
|
|
191
|
+
if (typeof request.system === "string") request.system = await encodeString(track, request.system);
|
|
192
|
+
if (typeof request.instructions === "string") request.instructions = await encodeString(track, request.instructions);
|
|
193
|
+
if (typeof request.input === "string") {
|
|
194
|
+
request.input = await encodeString(track, request.input);
|
|
195
|
+
} else if (Array.isArray(request.input)) {
|
|
196
|
+
for (let index = 0; index < request.input.length; index += 1) {
|
|
197
|
+
const item = request.input[index];
|
|
198
|
+
if (typeof item === "string") {
|
|
199
|
+
request.input[index] = await encodeString(track, item);
|
|
200
|
+
} else if (item) {
|
|
201
|
+
await encodeContentRecord(item, track);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (request.messages) {
|
|
206
|
+
for (const message of request.messages) {
|
|
207
|
+
await encodeMessage(message, track);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function encodeMessage(message, track) {
|
|
212
|
+
if (typeof message.content === "string") {
|
|
213
|
+
message.content = await encodeString(track, message.content);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
for (const part of message.content) {
|
|
217
|
+
if (part.type === "text") part.text = await encodeString(track, part.text);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function encodeContentRecord(record2, track) {
|
|
221
|
+
if (typeof record2.content === "string") {
|
|
222
|
+
record2.content = await encodeString(track, record2.content);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (!Array.isArray(record2.content)) return;
|
|
226
|
+
for (const part of record2.content) {
|
|
227
|
+
if (!isRecord(part) || typeof part.text !== "string") continue;
|
|
228
|
+
if (part.type === "text" || part.type === "input_text") {
|
|
229
|
+
part.text = await encodeString(track, part.text);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function wrapRequestTools(request, track) {
|
|
234
|
+
if (!request.functionTools) return;
|
|
235
|
+
request.functionTools = request.functionTools.map((tool) => {
|
|
236
|
+
if (!tool.executor) return tool;
|
|
237
|
+
const executor = tool.executor;
|
|
238
|
+
return {
|
|
239
|
+
...tool,
|
|
240
|
+
executor: async (args, context) => applyEncodedResult(track, await executor(args, context))
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
async function encodeString(track, value) {
|
|
245
|
+
const encoded = await applyEncodedResult(track, value);
|
|
246
|
+
return typeof encoded === "string" ? encoded : value;
|
|
247
|
+
}
|
|
248
|
+
async function applyEncodedResult(track, value) {
|
|
249
|
+
if (!track.enabled || !track.client) return value;
|
|
250
|
+
let prepared;
|
|
251
|
+
try {
|
|
252
|
+
prepared = await track.client.prepare(value);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
const failure = readPackageFailure(error);
|
|
255
|
+
if (failure?.code === "UNSUPPORTED_VALUE") return value;
|
|
256
|
+
throw mapAiInputError(error);
|
|
257
|
+
}
|
|
258
|
+
const wire = unframedBaseline(prepared);
|
|
259
|
+
note(track, prepared, value, wire);
|
|
260
|
+
if (wire === canonicalWire(value)) return value;
|
|
261
|
+
return wire;
|
|
262
|
+
}
|
|
263
|
+
function unframedBaseline(prepared) {
|
|
264
|
+
const baseline = isBaselineCodec(prepared.codec) && prepared.representation === "value";
|
|
265
|
+
if (!baseline || prepared.framing === "raw") return prepared.text;
|
|
266
|
+
const newline = prepared.text.indexOf("\n");
|
|
267
|
+
if (!prepared.text.startsWith("@ai-input/") || newline < 0) return prepared.text;
|
|
268
|
+
return prepared.text.slice(newline + 1);
|
|
269
|
+
}
|
|
270
|
+
function isBaselineCodec(codec) {
|
|
271
|
+
return codec === "json" || codec === "text" || codec === "outline";
|
|
272
|
+
}
|
|
273
|
+
function note(track, prepared, source, wire) {
|
|
274
|
+
const changed = wire !== canonicalWire(source);
|
|
275
|
+
if (changed) track.changed = true;
|
|
276
|
+
const alternate = prepared.codec === "toon" || prepared.representation !== "value";
|
|
277
|
+
if (alternate || !track.surfaced) {
|
|
278
|
+
track.codec = prepared.codec;
|
|
279
|
+
track.representation = prepared.representation;
|
|
280
|
+
track.approvalSource = prepared.receipt.approvalSource;
|
|
281
|
+
track.mode = prepared.mode;
|
|
282
|
+
}
|
|
283
|
+
if (alternate) track.surfaced = true;
|
|
284
|
+
}
|
|
285
|
+
function canonicalWire(value) {
|
|
286
|
+
if (typeof value === "string") return value;
|
|
287
|
+
return JSON.stringify(value);
|
|
288
|
+
}
|
|
289
|
+
function mapAiInputError(error) {
|
|
290
|
+
if (error instanceof AiDispatcherError) return error;
|
|
291
|
+
const failure = readPackageFailure(error);
|
|
292
|
+
if (failure) {
|
|
293
|
+
return new AiDispatcherError("AI_INPUT_FAILED", failure.message, {
|
|
294
|
+
code: failure.code,
|
|
295
|
+
...failure.path !== void 0 ? { path: failure.path } : {},
|
|
296
|
+
...failure.details !== void 0 ? { details: failure.details } : {}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const message = error instanceof Error ? error.message : "Input preparation failed.";
|
|
300
|
+
return new AiDispatcherError("AI_INPUT_FAILED", message);
|
|
301
|
+
}
|
|
302
|
+
function readPackageFailure(error) {
|
|
303
|
+
if (isAiInputError(error)) {
|
|
304
|
+
return {
|
|
305
|
+
code: error.code,
|
|
306
|
+
message: error.message,
|
|
307
|
+
...error.path !== void 0 ? { path: error.path } : {},
|
|
308
|
+
...error.details !== void 0 ? { details: error.details } : {}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (!(error instanceof Error) || error.name !== "AiInputError") return void 0;
|
|
312
|
+
const coded = error;
|
|
313
|
+
if (typeof coded.code !== "string") return void 0;
|
|
314
|
+
return {
|
|
315
|
+
code: coded.code,
|
|
316
|
+
message: error.message,
|
|
317
|
+
...typeof coded.path === "string" ? { path: coded.path } : {},
|
|
318
|
+
...coded.details !== void 0 ? { details: coded.details } : {}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function isRecord(value) {
|
|
322
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
323
|
+
}
|
|
324
|
+
|
|
13
325
|
// src/cache-usage.ts
|
|
14
326
|
function readCacheTokens(source) {
|
|
15
327
|
const cached = [];
|
|
@@ -58,7 +370,7 @@ function record(value) {
|
|
|
58
370
|
|
|
59
371
|
// src/json-pointer.ts
|
|
60
372
|
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
61
|
-
function
|
|
373
|
+
function isRecord2(value) {
|
|
62
374
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
375
|
}
|
|
64
376
|
function unescapeToken(token) {
|
|
@@ -85,7 +397,7 @@ function walk(current, tokens) {
|
|
|
85
397
|
if (index >= current.length) return [];
|
|
86
398
|
return walk(current[index], rest);
|
|
87
399
|
}
|
|
88
|
-
if (!
|
|
400
|
+
if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
|
|
89
401
|
return walk(current[head], rest);
|
|
90
402
|
}
|
|
91
403
|
function valuesAtPointer(root, pointer) {
|
|
@@ -122,7 +434,7 @@ function collectParents(current, tokens) {
|
|
|
122
434
|
if (index >= current.length) return [];
|
|
123
435
|
return collectParents(current[index], rest);
|
|
124
436
|
}
|
|
125
|
-
if (!
|
|
437
|
+
if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
|
|
126
438
|
return collectParents(current[head], rest);
|
|
127
439
|
}
|
|
128
440
|
function deletePointer(root, pointer) {
|
|
@@ -138,27 +450,27 @@ function deletePointer(root, pointer) {
|
|
|
138
450
|
if (index < parent.length) parent.splice(index, 1);
|
|
139
451
|
continue;
|
|
140
452
|
}
|
|
141
|
-
if (
|
|
453
|
+
if (isRecord2(parent) && Object.prototype.hasOwnProperty.call(parent, last)) {
|
|
142
454
|
delete parent[last];
|
|
143
455
|
}
|
|
144
456
|
}
|
|
145
457
|
}
|
|
146
458
|
|
|
147
459
|
// src/reasoning-extract.ts
|
|
148
|
-
function
|
|
460
|
+
function isRecord3(value) {
|
|
149
461
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
150
462
|
}
|
|
151
463
|
function textFromValue(value) {
|
|
152
464
|
if (typeof value === "string") return value;
|
|
153
465
|
if (Array.isArray(value)) return value.map(textFromValue).join("");
|
|
154
|
-
if (!
|
|
466
|
+
if (!isRecord3(value)) return "";
|
|
155
467
|
if (typeof value.text === "string") return value.text;
|
|
156
468
|
if (typeof value.summary === "string") return value.summary;
|
|
157
469
|
return "";
|
|
158
470
|
}
|
|
159
471
|
function extractStreamReasoningText(event, response) {
|
|
160
472
|
if (response.streamReasoningKeys.length > 0) {
|
|
161
|
-
if (!
|
|
473
|
+
if (!isRecord3(event)) return void 0;
|
|
162
474
|
for (const key of response.streamReasoningKeys) {
|
|
163
475
|
const text = textFromValue(event[key]);
|
|
164
476
|
if (text) return text;
|
|
@@ -256,6 +568,8 @@ function applyResolution(response, prepared) {
|
|
|
256
568
|
if (prepared.cacheWarnings?.length) {
|
|
257
569
|
result.warnings = [...result.warnings, ...prepared.cacheWarnings];
|
|
258
570
|
}
|
|
571
|
+
const inputResolution = prepared.inputTrack ? visibleInputResolution(prepared.inputTrack) : void 0;
|
|
572
|
+
if (inputResolution) result.inputResolution = inputResolution;
|
|
259
573
|
applyCacheUsage(result);
|
|
260
574
|
const directive = prepared.directive;
|
|
261
575
|
if (!directive) return result;
|
|
@@ -299,7 +613,7 @@ function applyUsage(response, raw, directive) {
|
|
|
299
613
|
}
|
|
300
614
|
|
|
301
615
|
// src/decorate-stream.ts
|
|
302
|
-
function
|
|
616
|
+
function isRecord4(value) {
|
|
303
617
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
304
618
|
}
|
|
305
619
|
async function* decorateStream(prepared, source) {
|
|
@@ -357,11 +671,13 @@ async function* decorateStream(prepared, source) {
|
|
|
357
671
|
};
|
|
358
672
|
}
|
|
359
673
|
}
|
|
360
|
-
const data =
|
|
674
|
+
const data = isRecord4(event.data) ? { ...event.data } : {};
|
|
361
675
|
if (splitter) data.text = splitter.answer;
|
|
362
676
|
if (reasoningText) data.reasoningText = reasoningText;
|
|
363
677
|
if (prepared.resolution) data.reasoningResolution = prepared.resolution;
|
|
364
678
|
if (prepared.cacheResolution) data.cacheResolution = prepared.cacheResolution;
|
|
679
|
+
const inputResolution = prepared.inputTrack ? visibleInputResolution(prepared.inputTrack) : void 0;
|
|
680
|
+
if (inputResolution) data.inputResolution = inputResolution;
|
|
365
681
|
yield { ...event, data };
|
|
366
682
|
continue;
|
|
367
683
|
}
|
|
@@ -369,18 +685,20 @@ async function* decorateStream(prepared, source) {
|
|
|
369
685
|
}
|
|
370
686
|
}
|
|
371
687
|
function withResolution(event, prepared) {
|
|
372
|
-
|
|
373
|
-
|
|
688
|
+
const inputResolution = prepared.inputTrack ? visibleInputResolution(prepared.inputTrack) : void 0;
|
|
689
|
+
if (!prepared.resolution && !prepared.cacheResolution && !inputResolution) return event;
|
|
690
|
+
const data = isRecord4(event.data) ? { ...event.data } : {};
|
|
374
691
|
if (prepared.resolution) data.reasoningResolution = prepared.resolution;
|
|
375
692
|
if (prepared.cacheResolution) data.cacheResolution = prepared.cacheResolution;
|
|
693
|
+
if (inputResolution) data.inputResolution = inputResolution;
|
|
376
694
|
return { ...event, data };
|
|
377
695
|
}
|
|
378
696
|
function textOf(event) {
|
|
379
|
-
if (!
|
|
697
|
+
if (!isRecord4(event.data) || typeof event.data.text !== "string") return "";
|
|
380
698
|
return event.data.text;
|
|
381
699
|
}
|
|
382
700
|
function adjustUsageEvent(event, prepared) {
|
|
383
|
-
if (!
|
|
701
|
+
if (!isRecord4(event.data) || !isRecord4(event.data.usage)) return event;
|
|
384
702
|
const paths = prepared.directive?.usage?.reasoningTokenPaths;
|
|
385
703
|
const usage = { ...event.data.usage };
|
|
386
704
|
let changed = false;
|
|
@@ -409,36 +727,6 @@ function adjustUsageEvent(event, prepared) {
|
|
|
409
727
|
return { ...event, data: { ...event.data, usage } };
|
|
410
728
|
}
|
|
411
729
|
|
|
412
|
-
// src/types/providers.ts
|
|
413
|
-
var IMPLEMENTED_AI_PROVIDERS = ["openrouter", "bedrock", "openai", "cloudflare"];
|
|
414
|
-
function isImplementedAiProvider(provider) {
|
|
415
|
-
return IMPLEMENTED_AI_PROVIDERS.includes(provider);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// src/errors.ts
|
|
419
|
-
var AiDispatcherError = class extends Error {
|
|
420
|
-
constructor(code, message, details) {
|
|
421
|
-
super(message);
|
|
422
|
-
this.code = code;
|
|
423
|
-
this.details = details;
|
|
424
|
-
this.name = "AiDispatcherError";
|
|
425
|
-
}
|
|
426
|
-
code;
|
|
427
|
-
details;
|
|
428
|
-
};
|
|
429
|
-
function providerNotImplemented(provider) {
|
|
430
|
-
const implemented = [...IMPLEMENTED_AI_PROVIDERS];
|
|
431
|
-
return new AiDispatcherError(
|
|
432
|
-
"PROVIDER_NOT_IMPLEMENTED",
|
|
433
|
-
`AI provider "${provider}" is not implemented yet. Supported by @x12i/ai-dispatcher: ${implemented.map((id) => `"${id}"`).join(", ")}.`,
|
|
434
|
-
{
|
|
435
|
-
provider,
|
|
436
|
-
implementedProviders: implemented,
|
|
437
|
-
operatorHint: `Pass provider: "${implemented[0]}" (or omit provider to use the default).`
|
|
438
|
-
}
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
730
|
// src/mcp.ts
|
|
443
731
|
var TOOL_TOKEN = /^[A-Za-z0-9_-]+$/;
|
|
444
732
|
var MAX_EXPOSED_NAME_LENGTH = 64;
|
|
@@ -460,7 +748,7 @@ function indexMcpServers(servers) {
|
|
|
460
748
|
for (const server of servers) {
|
|
461
749
|
assertServer(server, seenServers);
|
|
462
750
|
for (const tool of server.tools) {
|
|
463
|
-
if (!
|
|
751
|
+
if (!isRecord5(tool)) {
|
|
464
752
|
throw new AiDispatcherError(
|
|
465
753
|
"MCP_TOOL_NAME_INVALID",
|
|
466
754
|
`MCP server "${server.id}" has a tool that is not an object.`
|
|
@@ -547,18 +835,17 @@ function applyMcpCatalog(params) {
|
|
|
547
835
|
function createMcpExecutor(server, rawName) {
|
|
548
836
|
return async (args, context) => {
|
|
549
837
|
const result = await server.callTool(rawName, toArgs(args), context);
|
|
550
|
-
return
|
|
838
|
+
return mcpModelValue(result);
|
|
551
839
|
};
|
|
552
840
|
}
|
|
553
|
-
function
|
|
554
|
-
if (typeof result === "string") return result;
|
|
555
|
-
if (!isRecord4(result)) return stringify(result);
|
|
841
|
+
function mcpModelValue(result) {
|
|
842
|
+
if (typeof result === "string" || !isRecord5(result)) return result;
|
|
556
843
|
const text = textContent(result.content);
|
|
557
844
|
if (result.isError === true) {
|
|
558
845
|
throw new Error(text ?? stringify(result));
|
|
559
846
|
}
|
|
560
847
|
if (text !== void 0) return text;
|
|
561
|
-
return
|
|
848
|
+
return result;
|
|
562
849
|
}
|
|
563
850
|
function resolveAllowlist(filter, tools) {
|
|
564
851
|
if (filter === void 0) return void 0;
|
|
@@ -577,7 +864,7 @@ function resolveAllowlist(filter, tools) {
|
|
|
577
864
|
return new Set(filter);
|
|
578
865
|
}
|
|
579
866
|
function assertServer(server, seen) {
|
|
580
|
-
if (!
|
|
867
|
+
if (!isRecord5(server) || typeof server.id !== "string" || server.id.length === 0 || !Array.isArray(server.tools) || typeof server.callTool !== "function") {
|
|
581
868
|
throw new AiDispatcherError(
|
|
582
869
|
"MCP_SERVER_INVALID",
|
|
583
870
|
"An MCP server needs an id, a tools array, and a callTool function."
|
|
@@ -622,7 +909,7 @@ function toolDescription(tool) {
|
|
|
622
909
|
}
|
|
623
910
|
function toolParameters(tool) {
|
|
624
911
|
if (tool.inputSchema === void 0) return { ...DEFAULT_PARAMETERS, properties: {} };
|
|
625
|
-
if (!
|
|
912
|
+
if (!isRecord5(tool.inputSchema)) {
|
|
626
913
|
throw new AiDispatcherError(
|
|
627
914
|
"MCP_TOOL_NAME_INVALID",
|
|
628
915
|
`MCP tool "${tool.name}" inputSchema must be an object.`
|
|
@@ -634,14 +921,14 @@ function textContent(content) {
|
|
|
634
921
|
if (!Array.isArray(content)) return void 0;
|
|
635
922
|
const parts = [];
|
|
636
923
|
for (const part of content) {
|
|
637
|
-
if (!
|
|
924
|
+
if (!isRecord5(part) || part.type !== "text" || typeof part.text !== "string") continue;
|
|
638
925
|
parts.push(part.text);
|
|
639
926
|
}
|
|
640
927
|
if (!parts.length) return void 0;
|
|
641
928
|
return parts.join("\n");
|
|
642
929
|
}
|
|
643
930
|
function toArgs(args) {
|
|
644
|
-
if (
|
|
931
|
+
if (isRecord5(args)) return args;
|
|
645
932
|
return {};
|
|
646
933
|
}
|
|
647
934
|
function listFailed(serverId, error) {
|
|
@@ -653,7 +940,7 @@ function stringify(result) {
|
|
|
653
940
|
const serialized = JSON.stringify(result);
|
|
654
941
|
return serialized === void 0 ? "null" : serialized;
|
|
655
942
|
}
|
|
656
|
-
function
|
|
943
|
+
function isRecord5(value) {
|
|
657
944
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
658
945
|
}
|
|
659
946
|
|
|
@@ -1215,7 +1502,7 @@ function compileCloudflareRequest(request, options, call, streaming) {
|
|
|
1215
1502
|
if (call?.finalizeBody) body = call.finalizeBody(body);
|
|
1216
1503
|
if (endpoint !== "run") body = { ...body, stream: streaming };
|
|
1217
1504
|
const gateway = mergeGateway(options.gateway, call?.cloudflare?.gateway);
|
|
1218
|
-
const fromBody =
|
|
1505
|
+
const fromBody = isRecord6(body.metadata) ? body.metadata : void 0;
|
|
1219
1506
|
const packed = packProviderRecord(
|
|
1220
1507
|
request.metadata,
|
|
1221
1508
|
request.agentId,
|
|
@@ -1469,7 +1756,7 @@ function messagesFromInput(request) {
|
|
|
1469
1756
|
messages.push({ role: "user", content: item });
|
|
1470
1757
|
continue;
|
|
1471
1758
|
}
|
|
1472
|
-
if (!
|
|
1759
|
+
if (!isRecord6(item)) continue;
|
|
1473
1760
|
const role = item.role === "assistant" || item.role === "tool" || item.role === "system" ? item.role : "user";
|
|
1474
1761
|
if (role === "system") continue;
|
|
1475
1762
|
const content = inputContent(item.content);
|
|
@@ -1486,12 +1773,12 @@ function inputContent(content) {
|
|
|
1486
1773
|
if (!Array.isArray(content)) return void 0;
|
|
1487
1774
|
const parts = [];
|
|
1488
1775
|
for (const part of content) {
|
|
1489
|
-
if (!
|
|
1776
|
+
if (!isRecord6(part)) continue;
|
|
1490
1777
|
if ((part.type === "text" || part.type === "input_text") && typeof part.text === "string") {
|
|
1491
1778
|
parts.push({ type: "text", text: part.text });
|
|
1492
1779
|
} else if (part.type === "image_url" && typeof part.imageUrl === "string") {
|
|
1493
1780
|
parts.push({ type: "image_url", imageUrl: part.imageUrl });
|
|
1494
|
-
} else if (part.type === "image_url" &&
|
|
1781
|
+
} else if (part.type === "image_url" && isRecord6(part.image_url) && typeof part.image_url.url === "string") {
|
|
1495
1782
|
parts.push({ type: "image_url", imageUrl: part.image_url.url });
|
|
1496
1783
|
} else if (part.type === "input_image" && typeof part.image_url === "string") {
|
|
1497
1784
|
parts.push({ type: "image_url", imageUrl: part.image_url });
|
|
@@ -1710,7 +1997,7 @@ function continueMessages(body, response, results) {
|
|
|
1710
1997
|
return { ...body, messages };
|
|
1711
1998
|
}
|
|
1712
1999
|
function continueRun(body, response, results) {
|
|
1713
|
-
const input =
|
|
2000
|
+
const input = isRecord6(body.input) ? body.input : {};
|
|
1714
2001
|
const continued = continueChat({ messages: input.messages }, response, results);
|
|
1715
2002
|
return { ...body, input: { ...input, messages: continued.messages } };
|
|
1716
2003
|
}
|
|
@@ -1799,7 +2086,7 @@ async function* streamCloudflare(request, options, call) {
|
|
|
1799
2086
|
throw failure;
|
|
1800
2087
|
}
|
|
1801
2088
|
for await (const event of parseSse(response)) {
|
|
1802
|
-
if (!
|
|
2089
|
+
if (!isRecord6(event)) continue;
|
|
1803
2090
|
const catalogReasoning = call?.extractStreamReasoning?.(event);
|
|
1804
2091
|
const reasoningText = catalogReasoning || reasoningDelta(event);
|
|
1805
2092
|
if (reasoningText) yield { type: "stream.reasoning.delta", requestId, data: { text: reasoningText } };
|
|
@@ -1862,7 +2149,7 @@ async function sendOnce(compiled, options, timeoutMs) {
|
|
|
1862
2149
|
if (!response.ok) throw await readFailure(response);
|
|
1863
2150
|
const text = await response.text();
|
|
1864
2151
|
const parsed = text ? JSON.parse(text) : {};
|
|
1865
|
-
if (
|
|
2152
|
+
if (isRecord6(parsed) && parsed.success === false) {
|
|
1866
2153
|
throw new AiDispatcherError(
|
|
1867
2154
|
"PROVIDER_REQUEST_FAILED",
|
|
1868
2155
|
messageFrom(parsed) ?? "Cloudflare request failed.",
|
|
@@ -1913,7 +2200,7 @@ function isConfigError(code) {
|
|
|
1913
2200
|
}
|
|
1914
2201
|
function asTerminalError(error) {
|
|
1915
2202
|
if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
|
|
1916
|
-
const status =
|
|
2203
|
+
const status = isRecord6(error.details) ? error.details.status : void 0;
|
|
1917
2204
|
if (status === 429) return new AiDispatcherError("PROVIDER_RATE_LIMITED", error.message, error.details);
|
|
1918
2205
|
return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
|
|
1919
2206
|
}
|
|
@@ -1923,7 +2210,7 @@ function asTerminalError(error) {
|
|
|
1923
2210
|
function normalizeCloudflareResponse(params) {
|
|
1924
2211
|
const payload = unwrap(params.response);
|
|
1925
2212
|
const calls = params.functionTools ?? extractCalls(params.compiled.endpoint, params.response);
|
|
1926
|
-
const usage = normalizeUsage(payload.usage) ?? normalizeUsage(
|
|
2213
|
+
const usage = normalizeUsage(payload.usage) ?? normalizeUsage(isRecord6(params.response) ? params.response.usage : void 0);
|
|
1927
2214
|
const status = params.status ?? (calls.length ? "requires_action" : "completed");
|
|
1928
2215
|
return {
|
|
1929
2216
|
id: typeof payload.id === "string" ? payload.id : params.requestId,
|
|
@@ -1956,16 +2243,16 @@ function answerText(response) {
|
|
|
1956
2243
|
if (chat) return chat;
|
|
1957
2244
|
const anthropic = anthropicAnswer(response);
|
|
1958
2245
|
if (anthropic) return anthropic;
|
|
1959
|
-
if (
|
|
2246
|
+
if (isRecord6(response.result)) return answerText(response.result);
|
|
1960
2247
|
return "";
|
|
1961
2248
|
}
|
|
1962
2249
|
function responsesAnswer(response) {
|
|
1963
2250
|
if (!Array.isArray(response.output)) return "";
|
|
1964
2251
|
const parts = [];
|
|
1965
2252
|
for (const item of response.output) {
|
|
1966
|
-
if (!
|
|
2253
|
+
if (!isRecord6(item) || item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
1967
2254
|
for (const part of item.content) {
|
|
1968
|
-
if (
|
|
2255
|
+
if (isRecord6(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
|
|
1969
2256
|
}
|
|
1970
2257
|
}
|
|
1971
2258
|
return parts.join("");
|
|
@@ -1976,16 +2263,16 @@ function chatAnswer(response) {
|
|
|
1976
2263
|
const content = message.content;
|
|
1977
2264
|
if (typeof content === "string") return content;
|
|
1978
2265
|
if (!Array.isArray(content)) return "";
|
|
1979
|
-
return content.map((part) =>
|
|
2266
|
+
return content.map((part) => isRecord6(part) && typeof part.text === "string" ? part.text : "").filter(Boolean).join("");
|
|
1980
2267
|
}
|
|
1981
2268
|
function anthropicAnswer(response) {
|
|
1982
2269
|
if (!Array.isArray(response.content)) return "";
|
|
1983
|
-
return response.content.map((part) =>
|
|
2270
|
+
return response.content.map((part) => isRecord6(part) && part.type === "text" && typeof part.text === "string" ? part.text : "").filter(Boolean).join("");
|
|
1984
2271
|
}
|
|
1985
2272
|
function chatAssistantMessage(response) {
|
|
1986
|
-
if (!Array.isArray(response.choices) || !
|
|
2273
|
+
if (!Array.isArray(response.choices) || !isRecord6(response.choices[0])) return void 0;
|
|
1987
2274
|
const message = response.choices[0].message;
|
|
1988
|
-
return
|
|
2275
|
+
return isRecord6(message) ? message : void 0;
|
|
1989
2276
|
}
|
|
1990
2277
|
function extractCalls(endpoint, response) {
|
|
1991
2278
|
const record2 = unwrap(response);
|
|
@@ -1997,7 +2284,7 @@ function responsesCalls(response) {
|
|
|
1997
2284
|
if (!Array.isArray(response.output)) return [];
|
|
1998
2285
|
const calls = [];
|
|
1999
2286
|
for (const item of response.output) {
|
|
2000
|
-
if (!
|
|
2287
|
+
if (!isRecord6(item) || item.type !== "function_call") continue;
|
|
2001
2288
|
calls.push(callUsage(
|
|
2002
2289
|
typeof item.name === "string" ? item.name : "function",
|
|
2003
2290
|
typeof item.call_id === "string" ? item.call_id : "function",
|
|
@@ -2011,7 +2298,7 @@ function chatCalls(response) {
|
|
|
2011
2298
|
if (!message || !Array.isArray(message.tool_calls)) return [];
|
|
2012
2299
|
const calls = [];
|
|
2013
2300
|
for (const item of message.tool_calls) {
|
|
2014
|
-
if (!
|
|
2301
|
+
if (!isRecord6(item) || !isRecord6(item.function)) continue;
|
|
2015
2302
|
const name = typeof item.function.name === "string" ? item.function.name : "function";
|
|
2016
2303
|
calls.push(callUsage(
|
|
2017
2304
|
name,
|
|
@@ -2025,7 +2312,7 @@ function anthropicCalls(response) {
|
|
|
2025
2312
|
if (!Array.isArray(response.content)) return [];
|
|
2026
2313
|
const calls = [];
|
|
2027
2314
|
for (const item of response.content) {
|
|
2028
|
-
if (!
|
|
2315
|
+
if (!isRecord6(item) || item.type !== "tool_use") continue;
|
|
2029
2316
|
const name = typeof item.name === "string" ? item.name : "function";
|
|
2030
2317
|
calls.push({
|
|
2031
2318
|
name,
|
|
@@ -2064,7 +2351,7 @@ function emptyToolUsage(calls) {
|
|
|
2064
2351
|
};
|
|
2065
2352
|
}
|
|
2066
2353
|
function normalizeUsage(usage) {
|
|
2067
|
-
if (!
|
|
2354
|
+
if (!isRecord6(usage)) return void 0;
|
|
2068
2355
|
const inputTokens = numberValue(usage.input_tokens) ?? numberValue(usage.prompt_tokens);
|
|
2069
2356
|
const outputTokens = numberValue(usage.output_tokens) ?? numberValue(usage.completion_tokens);
|
|
2070
2357
|
const totalTokens = numberValue(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
|
|
@@ -2115,7 +2402,7 @@ function errorEvent(requestId, error) {
|
|
|
2115
2402
|
}
|
|
2116
2403
|
function textDelta(event) {
|
|
2117
2404
|
if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) return event.delta;
|
|
2118
|
-
if (event.type === "content_block_delta" &&
|
|
2405
|
+
if (event.type === "content_block_delta" && isRecord6(event.delta) && event.delta.type === "text_delta" && typeof event.delta.text === "string" && event.delta.text) {
|
|
2119
2406
|
return event.delta.text;
|
|
2120
2407
|
}
|
|
2121
2408
|
const choice = firstChoiceDelta(event);
|
|
@@ -2124,7 +2411,7 @@ function textDelta(event) {
|
|
|
2124
2411
|
}
|
|
2125
2412
|
function reasoningDelta(event) {
|
|
2126
2413
|
if (event.type === "response.reasoning_summary_text.delta" && typeof event.delta === "string" && event.delta) return event.delta;
|
|
2127
|
-
if (event.type === "content_block_delta" &&
|
|
2414
|
+
if (event.type === "content_block_delta" && isRecord6(event.delta) && event.delta.type === "thinking_delta" && typeof event.delta.thinking === "string" && event.delta.thinking) {
|
|
2128
2415
|
return event.delta.thinking;
|
|
2129
2416
|
}
|
|
2130
2417
|
const choice = firstChoiceDelta(event);
|
|
@@ -2141,13 +2428,13 @@ function toolCallDelta(event) {
|
|
|
2141
2428
|
argumentsDelta: event.delta
|
|
2142
2429
|
};
|
|
2143
2430
|
}
|
|
2144
|
-
if (event.type === "content_block_delta" &&
|
|
2431
|
+
if (event.type === "content_block_delta" && isRecord6(event.delta) && event.delta.type === "input_json_delta" && typeof event.delta.partial_json === "string") {
|
|
2145
2432
|
return { index: typeof event.index === "number" ? event.index : 0, argumentsDelta: event.delta.partial_json };
|
|
2146
2433
|
}
|
|
2147
2434
|
const choice = firstChoiceDelta(event);
|
|
2148
|
-
if (!choice || !Array.isArray(choice.tool_calls) || !
|
|
2435
|
+
if (!choice || !Array.isArray(choice.tool_calls) || !isRecord6(choice.tool_calls[0])) return void 0;
|
|
2149
2436
|
const tool = choice.tool_calls[0];
|
|
2150
|
-
const fn =
|
|
2437
|
+
const fn = isRecord6(tool.function) ? tool.function : void 0;
|
|
2151
2438
|
const args = fn && typeof fn.arguments === "string" ? fn.arguments : void 0;
|
|
2152
2439
|
if (!args && !(fn && typeof fn.name === "string")) return void 0;
|
|
2153
2440
|
return {
|
|
@@ -2157,18 +2444,18 @@ function toolCallDelta(event) {
|
|
|
2157
2444
|
};
|
|
2158
2445
|
}
|
|
2159
2446
|
function streamUsage(event) {
|
|
2160
|
-
if (event.type === "response.completed" &&
|
|
2161
|
-
if (
|
|
2447
|
+
if (event.type === "response.completed" && isRecord6(event.response)) return normalizeUsage(event.response.usage);
|
|
2448
|
+
if (isRecord6(event.usage)) return normalizeUsage(event.usage);
|
|
2162
2449
|
return void 0;
|
|
2163
2450
|
}
|
|
2164
2451
|
function firstChoiceDelta(event) {
|
|
2165
|
-
if (!Array.isArray(event.choices) || !
|
|
2452
|
+
if (!Array.isArray(event.choices) || !isRecord6(event.choices[0])) return void 0;
|
|
2166
2453
|
const delta = event.choices[0].delta;
|
|
2167
|
-
return
|
|
2454
|
+
return isRecord6(delta) ? delta : void 0;
|
|
2168
2455
|
}
|
|
2169
2456
|
function unwrap(response) {
|
|
2170
|
-
if (!
|
|
2171
|
-
if (response.success === true &&
|
|
2457
|
+
if (!isRecord6(response)) return {};
|
|
2458
|
+
if (response.success === true && isRecord6(response.result)) return response.result;
|
|
2172
2459
|
return response;
|
|
2173
2460
|
}
|
|
2174
2461
|
function modelOf(compiled) {
|
|
@@ -2176,12 +2463,12 @@ function modelOf(compiled) {
|
|
|
2176
2463
|
return "";
|
|
2177
2464
|
}
|
|
2178
2465
|
function messageFrom(body) {
|
|
2179
|
-
if (!
|
|
2466
|
+
if (!isRecord6(body)) return void 0;
|
|
2180
2467
|
if (typeof body.message === "string") return body.message;
|
|
2181
|
-
if (
|
|
2468
|
+
if (isRecord6(body.error) && typeof body.error.message === "string") return body.error.message;
|
|
2182
2469
|
if (Array.isArray(body.errors)) {
|
|
2183
2470
|
for (const error of body.errors) {
|
|
2184
|
-
if (
|
|
2471
|
+
if (isRecord6(error) && typeof error.message === "string") return error.message;
|
|
2185
2472
|
}
|
|
2186
2473
|
}
|
|
2187
2474
|
return void 0;
|
|
@@ -2322,7 +2609,7 @@ function readEnv(name) {
|
|
|
2322
2609
|
if (typeof process === "undefined") return void 0;
|
|
2323
2610
|
return process.env[name];
|
|
2324
2611
|
}
|
|
2325
|
-
function
|
|
2612
|
+
function isRecord6(value) {
|
|
2326
2613
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2327
2614
|
}
|
|
2328
2615
|
|
|
@@ -2530,7 +2817,7 @@ function settleNested(policy, kind, parentEffort) {
|
|
|
2530
2817
|
if (kind === "fusion") next.judgeModel = directive.model;
|
|
2531
2818
|
else next.model = directive.model;
|
|
2532
2819
|
}
|
|
2533
|
-
if (
|
|
2820
|
+
if (isRecord7(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
|
|
2534
2821
|
return next;
|
|
2535
2822
|
}
|
|
2536
2823
|
function applyPromptPrefix(request, prefix) {
|
|
@@ -2580,7 +2867,7 @@ function prefixInputItems(items, prefix) {
|
|
|
2580
2867
|
}
|
|
2581
2868
|
if (!Array.isArray(item.content)) continue;
|
|
2582
2869
|
for (const part of item.content) {
|
|
2583
|
-
if (!
|
|
2870
|
+
if (!isRecord7(part) || typeof part.text !== "string") continue;
|
|
2584
2871
|
if (part.type === "text" || part.type === "input_text") {
|
|
2585
2872
|
part.text = prefix + part.text;
|
|
2586
2873
|
return true;
|
|
@@ -2641,7 +2928,7 @@ var ROLE_STAMP_FIELDS = ["role", "roleLayer", "roleDigest", "profileChoice"];
|
|
|
2641
2928
|
function applyRoleStamp(request) {
|
|
2642
2929
|
const stamp = request.role;
|
|
2643
2930
|
delete request.role;
|
|
2644
|
-
if (!
|
|
2931
|
+
if (!isRecord7(stamp)) return;
|
|
2645
2932
|
const identity = {};
|
|
2646
2933
|
for (const field of ROLE_STAMP_FIELDS) {
|
|
2647
2934
|
const value = stamp[field];
|
|
@@ -2656,8 +2943,8 @@ function applyRoleStamp(request) {
|
|
|
2656
2943
|
}
|
|
2657
2944
|
function absorbOverrideMetadata(request) {
|
|
2658
2945
|
const overrides = request.rawOpenRouterOverrides;
|
|
2659
|
-
if (!
|
|
2660
|
-
if (
|
|
2946
|
+
if (!isRecord7(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
|
|
2947
|
+
if (isRecord7(overrides.metadata)) {
|
|
2661
2948
|
const current = { ...request.metadata ?? {} };
|
|
2662
2949
|
const extra = {};
|
|
2663
2950
|
for (const [key, value] of Object.entries(overrides.metadata)) {
|
|
@@ -2696,7 +2983,7 @@ function hasOwnKeys(value) {
|
|
|
2696
2983
|
function hasStreamMetadata(directive) {
|
|
2697
2984
|
return directive.response.streamReasoningKeys.length > 0 || directive.response.streamReasoningPaths.length > 0;
|
|
2698
2985
|
}
|
|
2699
|
-
function
|
|
2986
|
+
function isRecord7(value) {
|
|
2700
2987
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2701
2988
|
}
|
|
2702
2989
|
function readEnv2(name) {
|
|
@@ -3019,7 +3306,7 @@ async function runOpenAi(request, options, call, requestId) {
|
|
|
3019
3306
|
let response;
|
|
3020
3307
|
for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
|
|
3021
3308
|
response = await sendWithRetries2(active, options, timeoutFor2(request, options));
|
|
3022
|
-
const calls = functionCalls(
|
|
3309
|
+
const calls = functionCalls(isRecord8(response) ? response : {});
|
|
3023
3310
|
if (!calls.length) break;
|
|
3024
3311
|
if (!calls.every((item) => resolveExecutor2(item.name, request, options))) {
|
|
3025
3312
|
return normalizeOpenAiResponse({
|
|
@@ -3039,7 +3326,7 @@ async function runOpenAi(request, options, call, requestId) {
|
|
|
3039
3326
|
results.push(await executeOpenAiTool(item, request, options));
|
|
3040
3327
|
}
|
|
3041
3328
|
functionUsage.push(...results);
|
|
3042
|
-
const record2 =
|
|
3329
|
+
const record2 = isRecord8(response) ? response : {};
|
|
3043
3330
|
active = {
|
|
3044
3331
|
...active,
|
|
3045
3332
|
body: clean2({
|
|
@@ -3154,7 +3441,7 @@ async function* streamOpenAi(request, options, call) {
|
|
|
3154
3441
|
throw failure;
|
|
3155
3442
|
}
|
|
3156
3443
|
for await (const event of parseSse2(response)) {
|
|
3157
|
-
if (!
|
|
3444
|
+
if (!isRecord8(event)) continue;
|
|
3158
3445
|
if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) {
|
|
3159
3446
|
text += event.delta;
|
|
3160
3447
|
yield { type: "stream.text.delta", requestId, data: { text: event.delta } };
|
|
@@ -3173,7 +3460,7 @@ async function* streamOpenAi(request, options, call) {
|
|
|
3173
3460
|
argumentsDelta: event.delta
|
|
3174
3461
|
}
|
|
3175
3462
|
};
|
|
3176
|
-
} else if (event.type === "response.completed" &&
|
|
3463
|
+
} else if (event.type === "response.completed" && isRecord8(event.response)) {
|
|
3177
3464
|
const usage = normalizeUsage2(event.response.usage);
|
|
3178
3465
|
if (usage) yield { type: "stream.usage", requestId, data: { usage } };
|
|
3179
3466
|
} else if (event.type === "error") {
|
|
@@ -3267,7 +3554,7 @@ function isConfigError2(code) {
|
|
|
3267
3554
|
}
|
|
3268
3555
|
function asTerminalError2(error) {
|
|
3269
3556
|
if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
|
|
3270
|
-
const status =
|
|
3557
|
+
const status = isRecord8(error.details) ? error.details.status : void 0;
|
|
3271
3558
|
if (status === 429) return new AiDispatcherError("PROVIDER_RATE_LIMITED", error.message, error.details);
|
|
3272
3559
|
return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
|
|
3273
3560
|
}
|
|
@@ -3275,7 +3562,7 @@ function asTerminalError2(error) {
|
|
|
3275
3562
|
return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
3276
3563
|
}
|
|
3277
3564
|
function normalizeOpenAiResponse(params) {
|
|
3278
|
-
const record2 =
|
|
3565
|
+
const record2 = isRecord8(params.response) ? params.response : {};
|
|
3279
3566
|
const text = answerText2(record2);
|
|
3280
3567
|
const calls = params.functionTools ?? functionCalls(record2);
|
|
3281
3568
|
const usage = normalizeUsage2(record2.usage);
|
|
@@ -3302,9 +3589,9 @@ function answerText2(response) {
|
|
|
3302
3589
|
if (Array.isArray(output)) {
|
|
3303
3590
|
const parts = [];
|
|
3304
3591
|
for (const item of output) {
|
|
3305
|
-
if (!
|
|
3592
|
+
if (!isRecord8(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
3306
3593
|
for (const part of item.content) {
|
|
3307
|
-
if (
|
|
3594
|
+
if (isRecord8(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
|
|
3308
3595
|
}
|
|
3309
3596
|
}
|
|
3310
3597
|
if (parts.length > 0) return parts.join("");
|
|
@@ -3315,7 +3602,7 @@ function functionCalls(response) {
|
|
|
3315
3602
|
if (!Array.isArray(response.output)) return [];
|
|
3316
3603
|
const calls = [];
|
|
3317
3604
|
for (const item of response.output) {
|
|
3318
|
-
if (!
|
|
3605
|
+
if (!isRecord8(item) || item.type !== "function_call") continue;
|
|
3319
3606
|
const name = typeof item.name === "string" ? item.name : "function";
|
|
3320
3607
|
const callId = typeof item.call_id === "string" ? item.call_id : name;
|
|
3321
3608
|
let args = item.arguments;
|
|
@@ -3347,7 +3634,7 @@ function toolUsage(calls) {
|
|
|
3347
3634
|
};
|
|
3348
3635
|
}
|
|
3349
3636
|
function normalizeUsage2(usage) {
|
|
3350
|
-
if (!
|
|
3637
|
+
if (!isRecord8(usage)) return void 0;
|
|
3351
3638
|
const inputTokens = numberValue2(usage.input_tokens);
|
|
3352
3639
|
const outputTokens = numberValue2(usage.output_tokens);
|
|
3353
3640
|
const totalTokens = numberValue2(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
|
|
@@ -3399,16 +3686,16 @@ function modelOf2(compiled) {
|
|
|
3399
3686
|
return typeof compiled.body.model === "string" ? compiled.body.model : "";
|
|
3400
3687
|
}
|
|
3401
3688
|
function messageFrom2(body) {
|
|
3402
|
-
if (!
|
|
3689
|
+
if (!isRecord8(body)) return void 0;
|
|
3403
3690
|
if (typeof body.message === "string") return body.message;
|
|
3404
|
-
if (
|
|
3691
|
+
if (isRecord8(body.error) && typeof body.error.message === "string") return body.error.message;
|
|
3405
3692
|
return void 0;
|
|
3406
3693
|
}
|
|
3407
3694
|
function numberValue2(value) {
|
|
3408
3695
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3409
3696
|
}
|
|
3410
3697
|
function attachProviderMetadata(body, request, packer) {
|
|
3411
|
-
const fromBody =
|
|
3698
|
+
const fromBody = isRecord8(body.metadata) ? body.metadata : void 0;
|
|
3412
3699
|
const packed = packProviderRecord2(request.metadata, request.agentId, fromBody, packer);
|
|
3413
3700
|
if (packed.wire) return { body: { ...body, metadata: packed.wire }, ...packed.warning ? { warning: packed.warning } : {} };
|
|
3414
3701
|
if (body.metadata === void 0) return { body, ...packed.warning ? { warning: packed.warning } : {} };
|
|
@@ -3482,7 +3769,7 @@ function readEnv3(name) {
|
|
|
3482
3769
|
if (typeof process === "undefined") return void 0;
|
|
3483
3770
|
return process.env[name];
|
|
3484
3771
|
}
|
|
3485
|
-
function
|
|
3772
|
+
function isRecord8(value) {
|
|
3486
3773
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3487
3774
|
}
|
|
3488
3775
|
|
|
@@ -3552,6 +3839,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3552
3839
|
if (provider === "openrouter") {
|
|
3553
3840
|
const openrouterOptions = {
|
|
3554
3841
|
...options.openrouter,
|
|
3842
|
+
...options.openrouter?.tools ? { tools: wrapToolRegistry(options.openrouter.tools) } : {},
|
|
3555
3843
|
metadataPacker: packer,
|
|
3556
3844
|
...options.logger && !options.openrouter?.logger ? { logger: options.logger } : {}
|
|
3557
3845
|
};
|
|
@@ -3560,6 +3848,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3560
3848
|
if (provider === "bedrock") {
|
|
3561
3849
|
const bedrockOptions = {
|
|
3562
3850
|
...options.bedrock,
|
|
3851
|
+
...options.bedrock?.tools ? { tools: wrapToolRegistry(options.bedrock.tools) } : {},
|
|
3563
3852
|
metadataPacker: packer,
|
|
3564
3853
|
...options.logger && !options.bedrock?.logger ? { logger: options.logger } : {}
|
|
3565
3854
|
};
|
|
@@ -3568,6 +3857,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3568
3857
|
if (provider === "openai") {
|
|
3569
3858
|
const openaiOptions = {
|
|
3570
3859
|
...options.openai,
|
|
3860
|
+
...options.openai?.tools ? { tools: wrapToolRegistry(options.openai.tools) } : {},
|
|
3571
3861
|
...options.logger && !options.openai?.logger ? { logger: options.logger } : {}
|
|
3572
3862
|
};
|
|
3573
3863
|
return createOpenAiProviderAdapter(openaiOptions, packer);
|
|
@@ -3575,6 +3865,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3575
3865
|
if (provider === "cloudflare") {
|
|
3576
3866
|
const cloudflareOptions = {
|
|
3577
3867
|
...options.cloudflare,
|
|
3868
|
+
...options.cloudflare?.tools ? { tools: wrapToolRegistry(options.cloudflare.tools) } : {},
|
|
3578
3869
|
...options.logger && !options.cloudflare?.logger ? { logger: options.logger } : {}
|
|
3579
3870
|
};
|
|
3580
3871
|
return createCloudflareProviderAdapter(cloudflareOptions, packer);
|
|
@@ -3596,21 +3887,27 @@ function createAiDispatcher(options = {}) {
|
|
|
3596
3887
|
return {
|
|
3597
3888
|
provider: defaultProvider,
|
|
3598
3889
|
async run(request) {
|
|
3599
|
-
const prepared = prepare(request, true);
|
|
3890
|
+
const prepared = await prepare(request, true);
|
|
3600
3891
|
log(options, "ai-dispatcher.request.started", prepared, "run", false);
|
|
3601
|
-
const response = await
|
|
3892
|
+
const response = await inputStorage.run(
|
|
3893
|
+
prepared.inputTrack,
|
|
3894
|
+
() => getAdapter(prepared.provider).run(prepared.request, callOf(prepared.call))
|
|
3895
|
+
);
|
|
3602
3896
|
return applyResolution(response, prepared);
|
|
3603
3897
|
},
|
|
3604
3898
|
executeStreamingChat(request) {
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
prepared
|
|
3609
|
-
|
|
3610
|
-
|
|
3899
|
+
return (async function* () {
|
|
3900
|
+
const prepared = await prepare(request, false);
|
|
3901
|
+
log(options, "ai-dispatcher.request.started", prepared, "executeStreamingChat", true);
|
|
3902
|
+
if (prepared.inputTrack) inputStorage.enterWith(prepared.inputTrack);
|
|
3903
|
+
yield* decorateStream(
|
|
3904
|
+
prepared,
|
|
3905
|
+
getAdapter(prepared.provider).executeStreamingChat(prepared.request, callOf(prepared.call))
|
|
3906
|
+
);
|
|
3907
|
+
})();
|
|
3611
3908
|
},
|
|
3612
|
-
compile(request) {
|
|
3613
|
-
const prepared = prepare(request, true);
|
|
3909
|
+
async compile(request) {
|
|
3910
|
+
const prepared = await prepare(request, true);
|
|
3614
3911
|
log(options, "ai-dispatcher.request.compiled", prepared, "compile", false);
|
|
3615
3912
|
const compiled = getAdapter(prepared.provider).compile(prepared.request, callOf(prepared.call));
|
|
3616
3913
|
return attachCompileMeta(prepared, compiled);
|
|
@@ -3624,7 +3921,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3624
3921
|
return pending;
|
|
3625
3922
|
}
|
|
3626
3923
|
};
|
|
3627
|
-
function prepare(request, includeMcp) {
|
|
3924
|
+
async function prepare(request, includeMcp) {
|
|
3628
3925
|
const provider = resolveProvider(request.provider, defaultProvider);
|
|
3629
3926
|
const prepared = prepareDispatchRequest({ request, provider, options });
|
|
3630
3927
|
const tools = providerTools(provider, options);
|
|
@@ -3634,6 +3931,11 @@ function createAiDispatcher(options = {}) {
|
|
|
3634
3931
|
...tools ? { providerTools: tools } : {},
|
|
3635
3932
|
includeMcp
|
|
3636
3933
|
});
|
|
3934
|
+
prepared.inputTrack = await applyAiInput({
|
|
3935
|
+
request: prepared.request,
|
|
3936
|
+
effectiveModel: prepared.effectiveModel,
|
|
3937
|
+
factory: options.aiInput
|
|
3938
|
+
});
|
|
3637
3939
|
return prepared;
|
|
3638
3940
|
}
|
|
3639
3941
|
}
|
|
@@ -3651,7 +3953,8 @@ function callOf(call) {
|
|
|
3651
3953
|
}
|
|
3652
3954
|
function attachCompileMeta(prepared, compiled) {
|
|
3653
3955
|
const record2 = compiled;
|
|
3654
|
-
const headers =
|
|
3956
|
+
const headers = isRecord9(record2.headers) ? redactHeaders(record2.headers) : void 0;
|
|
3957
|
+
const inputResolution = prepared.inputTrack ? visibleInputResolution(prepared.inputTrack) : void 0;
|
|
3655
3958
|
return {
|
|
3656
3959
|
...record2,
|
|
3657
3960
|
...headers ? { headers } : {},
|
|
@@ -3660,6 +3963,7 @@ function attachCompileMeta(prepared, compiled) {
|
|
|
3660
3963
|
effectiveModel: prepared.effectiveModel,
|
|
3661
3964
|
...prepared.resolution ? { reasoningResolution: prepared.resolution } : {},
|
|
3662
3965
|
...prepared.cacheResolution ? { cacheResolution: prepared.cacheResolution } : {},
|
|
3966
|
+
...inputResolution ? { inputResolution } : {},
|
|
3663
3967
|
...prepared.cacheWarnings?.length ? { warnings: [...Array.isArray(record2.warnings) ? record2.warnings : [], ...prepared.cacheWarnings] } : {}
|
|
3664
3968
|
};
|
|
3665
3969
|
}
|
|
@@ -3685,7 +3989,7 @@ function log(options, event, prepared, entrypoint, streaming) {
|
|
|
3685
3989
|
if (event.endsWith("compiled")) options.logger?.debug?.(event, payload);
|
|
3686
3990
|
else options.logger?.info?.(event, payload);
|
|
3687
3991
|
}
|
|
3688
|
-
function
|
|
3992
|
+
function isRecord9(value) {
|
|
3689
3993
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3690
3994
|
}
|
|
3691
3995
|
|