@x12i/ai-dispatcher 2.4.1 → 3.1.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 +31 -8
- package/dist/index.cjs +441 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -3
- package/dist/index.d.ts +77 -3
- package/dist/index.js +434 -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
|
|
|
@@ -2332,6 +2619,7 @@ function prepareDispatchRequest(params) {
|
|
|
2332
2619
|
applyRoleStamp(cloned);
|
|
2333
2620
|
applyDispatchIdentity(cloned);
|
|
2334
2621
|
absorbOverrideMetadata(cloned);
|
|
2622
|
+
applyDispatchGate(cloned);
|
|
2335
2623
|
const provider = params.provider;
|
|
2336
2624
|
const reasoningEffort = cloned.reasoningEffort;
|
|
2337
2625
|
const bedrockControls = cloned.bedrockControls;
|
|
@@ -2530,7 +2818,7 @@ function settleNested(policy, kind, parentEffort) {
|
|
|
2530
2818
|
if (kind === "fusion") next.judgeModel = directive.model;
|
|
2531
2819
|
else next.model = directive.model;
|
|
2532
2820
|
}
|
|
2533
|
-
if (
|
|
2821
|
+
if (isRecord7(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
|
|
2534
2822
|
return next;
|
|
2535
2823
|
}
|
|
2536
2824
|
function applyPromptPrefix(request, prefix) {
|
|
@@ -2580,7 +2868,7 @@ function prefixInputItems(items, prefix) {
|
|
|
2580
2868
|
}
|
|
2581
2869
|
if (!Array.isArray(item.content)) continue;
|
|
2582
2870
|
for (const part of item.content) {
|
|
2583
|
-
if (!
|
|
2871
|
+
if (!isRecord7(part) || typeof part.text !== "string") continue;
|
|
2584
2872
|
if (part.type === "text" || part.type === "input_text") {
|
|
2585
2873
|
part.text = prefix + part.text;
|
|
2586
2874
|
return true;
|
|
@@ -2641,7 +2929,7 @@ var ROLE_STAMP_FIELDS = ["role", "roleLayer", "roleDigest", "profileChoice"];
|
|
|
2641
2929
|
function applyRoleStamp(request) {
|
|
2642
2930
|
const stamp = request.role;
|
|
2643
2931
|
delete request.role;
|
|
2644
|
-
if (!
|
|
2932
|
+
if (!isRecord7(stamp)) return;
|
|
2645
2933
|
const identity = {};
|
|
2646
2934
|
for (const field of ROLE_STAMP_FIELDS) {
|
|
2647
2935
|
const value = stamp[field];
|
|
@@ -2656,8 +2944,8 @@ function applyRoleStamp(request) {
|
|
|
2656
2944
|
}
|
|
2657
2945
|
function absorbOverrideMetadata(request) {
|
|
2658
2946
|
const overrides = request.rawOpenRouterOverrides;
|
|
2659
|
-
if (!
|
|
2660
|
-
if (
|
|
2947
|
+
if (!isRecord7(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
|
|
2948
|
+
if (isRecord7(overrides.metadata)) {
|
|
2661
2949
|
const current = { ...request.metadata ?? {} };
|
|
2662
2950
|
const extra = {};
|
|
2663
2951
|
for (const [key, value] of Object.entries(overrides.metadata)) {
|
|
@@ -2684,6 +2972,12 @@ function applyDispatchIdentity(request) {
|
|
|
2684
2972
|
for (const key of Object.keys(identity)) delete rest[key];
|
|
2685
2973
|
request.metadata = { ...identity, ...rest };
|
|
2686
2974
|
}
|
|
2975
|
+
var AI_DISPATCHER_GATE = "ai-dispatcher";
|
|
2976
|
+
function applyDispatchGate(request) {
|
|
2977
|
+
const rest = { ...request.metadata ?? {} };
|
|
2978
|
+
delete rest["xtivix.gate"];
|
|
2979
|
+
request.metadata = { "xtivix.gate": AI_DISPATCHER_GATE, ...rest };
|
|
2980
|
+
}
|
|
2687
2981
|
function cloneRequest(request) {
|
|
2688
2982
|
const functionTools = request.functionTools?.map((tool) => ({ ...tool }));
|
|
2689
2983
|
const cloned = JSON.parse(JSON.stringify(request));
|
|
@@ -2696,7 +2990,7 @@ function hasOwnKeys(value) {
|
|
|
2696
2990
|
function hasStreamMetadata(directive) {
|
|
2697
2991
|
return directive.response.streamReasoningKeys.length > 0 || directive.response.streamReasoningPaths.length > 0;
|
|
2698
2992
|
}
|
|
2699
|
-
function
|
|
2993
|
+
function isRecord7(value) {
|
|
2700
2994
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2701
2995
|
}
|
|
2702
2996
|
function readEnv2(name) {
|
|
@@ -3019,7 +3313,7 @@ async function runOpenAi(request, options, call, requestId) {
|
|
|
3019
3313
|
let response;
|
|
3020
3314
|
for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
|
|
3021
3315
|
response = await sendWithRetries2(active, options, timeoutFor2(request, options));
|
|
3022
|
-
const calls = functionCalls(
|
|
3316
|
+
const calls = functionCalls(isRecord8(response) ? response : {});
|
|
3023
3317
|
if (!calls.length) break;
|
|
3024
3318
|
if (!calls.every((item) => resolveExecutor2(item.name, request, options))) {
|
|
3025
3319
|
return normalizeOpenAiResponse({
|
|
@@ -3039,7 +3333,7 @@ async function runOpenAi(request, options, call, requestId) {
|
|
|
3039
3333
|
results.push(await executeOpenAiTool(item, request, options));
|
|
3040
3334
|
}
|
|
3041
3335
|
functionUsage.push(...results);
|
|
3042
|
-
const record2 =
|
|
3336
|
+
const record2 = isRecord8(response) ? response : {};
|
|
3043
3337
|
active = {
|
|
3044
3338
|
...active,
|
|
3045
3339
|
body: clean2({
|
|
@@ -3154,7 +3448,7 @@ async function* streamOpenAi(request, options, call) {
|
|
|
3154
3448
|
throw failure;
|
|
3155
3449
|
}
|
|
3156
3450
|
for await (const event of parseSse2(response)) {
|
|
3157
|
-
if (!
|
|
3451
|
+
if (!isRecord8(event)) continue;
|
|
3158
3452
|
if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) {
|
|
3159
3453
|
text += event.delta;
|
|
3160
3454
|
yield { type: "stream.text.delta", requestId, data: { text: event.delta } };
|
|
@@ -3173,7 +3467,7 @@ async function* streamOpenAi(request, options, call) {
|
|
|
3173
3467
|
argumentsDelta: event.delta
|
|
3174
3468
|
}
|
|
3175
3469
|
};
|
|
3176
|
-
} else if (event.type === "response.completed" &&
|
|
3470
|
+
} else if (event.type === "response.completed" && isRecord8(event.response)) {
|
|
3177
3471
|
const usage = normalizeUsage2(event.response.usage);
|
|
3178
3472
|
if (usage) yield { type: "stream.usage", requestId, data: { usage } };
|
|
3179
3473
|
} else if (event.type === "error") {
|
|
@@ -3267,7 +3561,7 @@ function isConfigError2(code) {
|
|
|
3267
3561
|
}
|
|
3268
3562
|
function asTerminalError2(error) {
|
|
3269
3563
|
if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
|
|
3270
|
-
const status =
|
|
3564
|
+
const status = isRecord8(error.details) ? error.details.status : void 0;
|
|
3271
3565
|
if (status === 429) return new AiDispatcherError("PROVIDER_RATE_LIMITED", error.message, error.details);
|
|
3272
3566
|
return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
|
|
3273
3567
|
}
|
|
@@ -3275,7 +3569,7 @@ function asTerminalError2(error) {
|
|
|
3275
3569
|
return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
3276
3570
|
}
|
|
3277
3571
|
function normalizeOpenAiResponse(params) {
|
|
3278
|
-
const record2 =
|
|
3572
|
+
const record2 = isRecord8(params.response) ? params.response : {};
|
|
3279
3573
|
const text = answerText2(record2);
|
|
3280
3574
|
const calls = params.functionTools ?? functionCalls(record2);
|
|
3281
3575
|
const usage = normalizeUsage2(record2.usage);
|
|
@@ -3302,9 +3596,9 @@ function answerText2(response) {
|
|
|
3302
3596
|
if (Array.isArray(output)) {
|
|
3303
3597
|
const parts = [];
|
|
3304
3598
|
for (const item of output) {
|
|
3305
|
-
if (!
|
|
3599
|
+
if (!isRecord8(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
3306
3600
|
for (const part of item.content) {
|
|
3307
|
-
if (
|
|
3601
|
+
if (isRecord8(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
|
|
3308
3602
|
}
|
|
3309
3603
|
}
|
|
3310
3604
|
if (parts.length > 0) return parts.join("");
|
|
@@ -3315,7 +3609,7 @@ function functionCalls(response) {
|
|
|
3315
3609
|
if (!Array.isArray(response.output)) return [];
|
|
3316
3610
|
const calls = [];
|
|
3317
3611
|
for (const item of response.output) {
|
|
3318
|
-
if (!
|
|
3612
|
+
if (!isRecord8(item) || item.type !== "function_call") continue;
|
|
3319
3613
|
const name = typeof item.name === "string" ? item.name : "function";
|
|
3320
3614
|
const callId = typeof item.call_id === "string" ? item.call_id : name;
|
|
3321
3615
|
let args = item.arguments;
|
|
@@ -3347,7 +3641,7 @@ function toolUsage(calls) {
|
|
|
3347
3641
|
};
|
|
3348
3642
|
}
|
|
3349
3643
|
function normalizeUsage2(usage) {
|
|
3350
|
-
if (!
|
|
3644
|
+
if (!isRecord8(usage)) return void 0;
|
|
3351
3645
|
const inputTokens = numberValue2(usage.input_tokens);
|
|
3352
3646
|
const outputTokens = numberValue2(usage.output_tokens);
|
|
3353
3647
|
const totalTokens = numberValue2(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
|
|
@@ -3399,16 +3693,16 @@ function modelOf2(compiled) {
|
|
|
3399
3693
|
return typeof compiled.body.model === "string" ? compiled.body.model : "";
|
|
3400
3694
|
}
|
|
3401
3695
|
function messageFrom2(body) {
|
|
3402
|
-
if (!
|
|
3696
|
+
if (!isRecord8(body)) return void 0;
|
|
3403
3697
|
if (typeof body.message === "string") return body.message;
|
|
3404
|
-
if (
|
|
3698
|
+
if (isRecord8(body.error) && typeof body.error.message === "string") return body.error.message;
|
|
3405
3699
|
return void 0;
|
|
3406
3700
|
}
|
|
3407
3701
|
function numberValue2(value) {
|
|
3408
3702
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3409
3703
|
}
|
|
3410
3704
|
function attachProviderMetadata(body, request, packer) {
|
|
3411
|
-
const fromBody =
|
|
3705
|
+
const fromBody = isRecord8(body.metadata) ? body.metadata : void 0;
|
|
3412
3706
|
const packed = packProviderRecord2(request.metadata, request.agentId, fromBody, packer);
|
|
3413
3707
|
if (packed.wire) return { body: { ...body, metadata: packed.wire }, ...packed.warning ? { warning: packed.warning } : {} };
|
|
3414
3708
|
if (body.metadata === void 0) return { body, ...packed.warning ? { warning: packed.warning } : {} };
|
|
@@ -3482,7 +3776,7 @@ function readEnv3(name) {
|
|
|
3482
3776
|
if (typeof process === "undefined") return void 0;
|
|
3483
3777
|
return process.env[name];
|
|
3484
3778
|
}
|
|
3485
|
-
function
|
|
3779
|
+
function isRecord8(value) {
|
|
3486
3780
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3487
3781
|
}
|
|
3488
3782
|
|
|
@@ -3552,6 +3846,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3552
3846
|
if (provider === "openrouter") {
|
|
3553
3847
|
const openrouterOptions = {
|
|
3554
3848
|
...options.openrouter,
|
|
3849
|
+
...options.openrouter?.tools ? { tools: wrapToolRegistry(options.openrouter.tools) } : {},
|
|
3555
3850
|
metadataPacker: packer,
|
|
3556
3851
|
...options.logger && !options.openrouter?.logger ? { logger: options.logger } : {}
|
|
3557
3852
|
};
|
|
@@ -3560,6 +3855,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3560
3855
|
if (provider === "bedrock") {
|
|
3561
3856
|
const bedrockOptions = {
|
|
3562
3857
|
...options.bedrock,
|
|
3858
|
+
...options.bedrock?.tools ? { tools: wrapToolRegistry(options.bedrock.tools) } : {},
|
|
3563
3859
|
metadataPacker: packer,
|
|
3564
3860
|
...options.logger && !options.bedrock?.logger ? { logger: options.logger } : {}
|
|
3565
3861
|
};
|
|
@@ -3568,6 +3864,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3568
3864
|
if (provider === "openai") {
|
|
3569
3865
|
const openaiOptions = {
|
|
3570
3866
|
...options.openai,
|
|
3867
|
+
...options.openai?.tools ? { tools: wrapToolRegistry(options.openai.tools) } : {},
|
|
3571
3868
|
...options.logger && !options.openai?.logger ? { logger: options.logger } : {}
|
|
3572
3869
|
};
|
|
3573
3870
|
return createOpenAiProviderAdapter(openaiOptions, packer);
|
|
@@ -3575,6 +3872,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3575
3872
|
if (provider === "cloudflare") {
|
|
3576
3873
|
const cloudflareOptions = {
|
|
3577
3874
|
...options.cloudflare,
|
|
3875
|
+
...options.cloudflare?.tools ? { tools: wrapToolRegistry(options.cloudflare.tools) } : {},
|
|
3578
3876
|
...options.logger && !options.cloudflare?.logger ? { logger: options.logger } : {}
|
|
3579
3877
|
};
|
|
3580
3878
|
return createCloudflareProviderAdapter(cloudflareOptions, packer);
|
|
@@ -3596,21 +3894,27 @@ function createAiDispatcher(options = {}) {
|
|
|
3596
3894
|
return {
|
|
3597
3895
|
provider: defaultProvider,
|
|
3598
3896
|
async run(request) {
|
|
3599
|
-
const prepared = prepare(request, true);
|
|
3897
|
+
const prepared = await prepare(request, true);
|
|
3600
3898
|
log(options, "ai-dispatcher.request.started", prepared, "run", false);
|
|
3601
|
-
const response = await
|
|
3899
|
+
const response = await inputStorage.run(
|
|
3900
|
+
prepared.inputTrack,
|
|
3901
|
+
() => getAdapter(prepared.provider).run(prepared.request, callOf(prepared.call))
|
|
3902
|
+
);
|
|
3602
3903
|
return applyResolution(response, prepared);
|
|
3603
3904
|
},
|
|
3604
3905
|
executeStreamingChat(request) {
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
prepared
|
|
3609
|
-
|
|
3610
|
-
|
|
3906
|
+
return (async function* () {
|
|
3907
|
+
const prepared = await prepare(request, false);
|
|
3908
|
+
log(options, "ai-dispatcher.request.started", prepared, "executeStreamingChat", true);
|
|
3909
|
+
if (prepared.inputTrack) inputStorage.enterWith(prepared.inputTrack);
|
|
3910
|
+
yield* decorateStream(
|
|
3911
|
+
prepared,
|
|
3912
|
+
getAdapter(prepared.provider).executeStreamingChat(prepared.request, callOf(prepared.call))
|
|
3913
|
+
);
|
|
3914
|
+
})();
|
|
3611
3915
|
},
|
|
3612
|
-
compile(request) {
|
|
3613
|
-
const prepared = prepare(request, true);
|
|
3916
|
+
async compile(request) {
|
|
3917
|
+
const prepared = await prepare(request, true);
|
|
3614
3918
|
log(options, "ai-dispatcher.request.compiled", prepared, "compile", false);
|
|
3615
3919
|
const compiled = getAdapter(prepared.provider).compile(prepared.request, callOf(prepared.call));
|
|
3616
3920
|
return attachCompileMeta(prepared, compiled);
|
|
@@ -3624,7 +3928,7 @@ function createAiDispatcher(options = {}) {
|
|
|
3624
3928
|
return pending;
|
|
3625
3929
|
}
|
|
3626
3930
|
};
|
|
3627
|
-
function prepare(request, includeMcp) {
|
|
3931
|
+
async function prepare(request, includeMcp) {
|
|
3628
3932
|
const provider = resolveProvider(request.provider, defaultProvider);
|
|
3629
3933
|
const prepared = prepareDispatchRequest({ request, provider, options });
|
|
3630
3934
|
const tools = providerTools(provider, options);
|
|
@@ -3634,6 +3938,11 @@ function createAiDispatcher(options = {}) {
|
|
|
3634
3938
|
...tools ? { providerTools: tools } : {},
|
|
3635
3939
|
includeMcp
|
|
3636
3940
|
});
|
|
3941
|
+
prepared.inputTrack = await applyAiInput({
|
|
3942
|
+
request: prepared.request,
|
|
3943
|
+
effectiveModel: prepared.effectiveModel,
|
|
3944
|
+
factory: options.aiInput
|
|
3945
|
+
});
|
|
3637
3946
|
return prepared;
|
|
3638
3947
|
}
|
|
3639
3948
|
}
|
|
@@ -3651,7 +3960,8 @@ function callOf(call) {
|
|
|
3651
3960
|
}
|
|
3652
3961
|
function attachCompileMeta(prepared, compiled) {
|
|
3653
3962
|
const record2 = compiled;
|
|
3654
|
-
const headers =
|
|
3963
|
+
const headers = isRecord9(record2.headers) ? redactHeaders(record2.headers) : void 0;
|
|
3964
|
+
const inputResolution = prepared.inputTrack ? visibleInputResolution(prepared.inputTrack) : void 0;
|
|
3655
3965
|
return {
|
|
3656
3966
|
...record2,
|
|
3657
3967
|
...headers ? { headers } : {},
|
|
@@ -3660,6 +3970,7 @@ function attachCompileMeta(prepared, compiled) {
|
|
|
3660
3970
|
effectiveModel: prepared.effectiveModel,
|
|
3661
3971
|
...prepared.resolution ? { reasoningResolution: prepared.resolution } : {},
|
|
3662
3972
|
...prepared.cacheResolution ? { cacheResolution: prepared.cacheResolution } : {},
|
|
3973
|
+
...inputResolution ? { inputResolution } : {},
|
|
3663
3974
|
...prepared.cacheWarnings?.length ? { warnings: [...Array.isArray(record2.warnings) ? record2.warnings : [], ...prepared.cacheWarnings] } : {}
|
|
3664
3975
|
};
|
|
3665
3976
|
}
|
|
@@ -3685,7 +3996,7 @@ function log(options, event, prepared, entrypoint, streaming) {
|
|
|
3685
3996
|
if (event.endsWith("compiled")) options.logger?.debug?.(event, payload);
|
|
3686
3997
|
else options.logger?.info?.(event, payload);
|
|
3687
3998
|
}
|
|
3688
|
-
function
|
|
3999
|
+
function isRecord9(value) {
|
|
3689
4000
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3690
4001
|
}
|
|
3691
4002
|
|
|
@@ -3696,6 +4007,7 @@ function stripReasoningHistoryFields(message, paths) {
|
|
|
3696
4007
|
return clone;
|
|
3697
4008
|
}
|
|
3698
4009
|
export {
|
|
4010
|
+
AI_DISPATCHER_GATE,
|
|
3699
4011
|
AiDispatcherError,
|
|
3700
4012
|
DEFAULT_METADATA_PRIORITY,
|
|
3701
4013
|
IMPLEMENTED_AI_PROVIDERS,
|