@x12i/ai-dispatcher 1.4.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/LICENSE +21 -0
- package/README.md +363 -0
- package/dist/index.cjs +1761 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +164 -0
- package/dist/index.d.ts +164 -0
- package/dist/index.js +1736 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1761 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AiDispatcherError: () => AiDispatcherError,
|
|
24
|
+
IMPLEMENTED_AI_PROVIDERS: () => IMPLEMENTED_AI_PROVIDERS,
|
|
25
|
+
createAiDispatcher: () => createAiDispatcher,
|
|
26
|
+
isImplementedAiProvider: () => isImplementedAiProvider,
|
|
27
|
+
providerNotImplemented: () => providerNotImplemented,
|
|
28
|
+
resolveProvider: () => resolveProvider,
|
|
29
|
+
stripReasoningHistoryFields: () => stripReasoningHistoryFields
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/json-pointer.ts
|
|
34
|
+
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
35
|
+
function isRecord(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
function unescapeToken(token) {
|
|
39
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
40
|
+
}
|
|
41
|
+
function tokensFor(pointer) {
|
|
42
|
+
if (pointer === "") return [];
|
|
43
|
+
if (!pointer.startsWith("/")) return void 0;
|
|
44
|
+
const tokens = pointer.split("/").slice(1).map(unescapeToken);
|
|
45
|
+
if (tokens.some((token) => UNSAFE_KEYS.has(token))) return void 0;
|
|
46
|
+
return tokens;
|
|
47
|
+
}
|
|
48
|
+
function walk(current, tokens) {
|
|
49
|
+
if (tokens.length === 0) return [current];
|
|
50
|
+
const [head, ...rest] = tokens;
|
|
51
|
+
if (head === void 0) return [];
|
|
52
|
+
if (head === "*") {
|
|
53
|
+
if (!Array.isArray(current)) return [];
|
|
54
|
+
return current.flatMap((item) => walk(item, rest));
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(current)) {
|
|
57
|
+
if (!/^\d+$/.test(head)) return [];
|
|
58
|
+
const index = Number(head);
|
|
59
|
+
if (index >= current.length) return [];
|
|
60
|
+
return walk(current[index], rest);
|
|
61
|
+
}
|
|
62
|
+
if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
|
|
63
|
+
return walk(current[head], rest);
|
|
64
|
+
}
|
|
65
|
+
function valuesAtPointer(root, pointer) {
|
|
66
|
+
const tokens = tokensFor(pointer);
|
|
67
|
+
if (!tokens) return [];
|
|
68
|
+
return walk(root, tokens);
|
|
69
|
+
}
|
|
70
|
+
function firstConcatenatedText(root, paths) {
|
|
71
|
+
for (const path of paths) {
|
|
72
|
+
const strings = valuesAtPointer(root, path).filter((value) => typeof value === "string" && value.length > 0);
|
|
73
|
+
if (strings.length > 0) return strings.join("");
|
|
74
|
+
}
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
function firstNumber(root, paths) {
|
|
78
|
+
for (const path of paths) {
|
|
79
|
+
for (const value of valuesAtPointer(root, path)) {
|
|
80
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
function collectParents(current, tokens) {
|
|
86
|
+
if (tokens.length === 0) return [current];
|
|
87
|
+
const [head, ...rest] = tokens;
|
|
88
|
+
if (head === void 0) return [];
|
|
89
|
+
if (head === "*") {
|
|
90
|
+
if (!Array.isArray(current)) return [];
|
|
91
|
+
return current.flatMap((item) => collectParents(item, rest));
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(current)) {
|
|
94
|
+
if (!/^\d+$/.test(head)) return [];
|
|
95
|
+
const index = Number(head);
|
|
96
|
+
if (index >= current.length) return [];
|
|
97
|
+
return collectParents(current[index], rest);
|
|
98
|
+
}
|
|
99
|
+
if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
|
|
100
|
+
return collectParents(current[head], rest);
|
|
101
|
+
}
|
|
102
|
+
function deletePointer(root, pointer) {
|
|
103
|
+
const tokens = tokensFor(pointer);
|
|
104
|
+
if (!tokens || tokens.length === 0) return;
|
|
105
|
+
const last = tokens[tokens.length - 1];
|
|
106
|
+
if (last === void 0 || last === "*") return;
|
|
107
|
+
const parents = collectParents(root, tokens.slice(0, -1));
|
|
108
|
+
for (const parent of parents) {
|
|
109
|
+
if (Array.isArray(parent)) {
|
|
110
|
+
if (!/^\d+$/.test(last)) continue;
|
|
111
|
+
const index = Number(last);
|
|
112
|
+
if (index < parent.length) parent.splice(index, 1);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (isRecord(parent) && Object.prototype.hasOwnProperty.call(parent, last)) {
|
|
116
|
+
delete parent[last];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/reasoning-extract.ts
|
|
122
|
+
function isRecord2(value) {
|
|
123
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
124
|
+
}
|
|
125
|
+
function textFromValue(value) {
|
|
126
|
+
if (typeof value === "string") return value;
|
|
127
|
+
if (Array.isArray(value)) return value.map(textFromValue).join("");
|
|
128
|
+
if (!isRecord2(value)) return "";
|
|
129
|
+
if (typeof value.text === "string") return value.text;
|
|
130
|
+
if (typeof value.summary === "string") return value.summary;
|
|
131
|
+
return "";
|
|
132
|
+
}
|
|
133
|
+
function extractStreamReasoningText(event, response) {
|
|
134
|
+
if (response.streamReasoningKeys.length > 0) {
|
|
135
|
+
if (!isRecord2(event)) return void 0;
|
|
136
|
+
for (const key of response.streamReasoningKeys) {
|
|
137
|
+
const text = textFromValue(event[key]);
|
|
138
|
+
if (text) return text;
|
|
139
|
+
}
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
return firstConcatenatedText(event, response.streamReasoningPaths);
|
|
143
|
+
}
|
|
144
|
+
function readReasoningTokens(root, paths) {
|
|
145
|
+
for (const path of paths) {
|
|
146
|
+
const absolute = firstNumber(root, [path]);
|
|
147
|
+
if (absolute !== void 0) return absolute;
|
|
148
|
+
if (path.startsWith("/usage/")) {
|
|
149
|
+
const relative = firstNumber(root, [path.slice("/usage".length)]);
|
|
150
|
+
if (relative !== void 0) return relative;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/think-tags.ts
|
|
157
|
+
var OPEN = "<think>";
|
|
158
|
+
var CLOSE = "</think>";
|
|
159
|
+
function holdSuffix(buffer, tag) {
|
|
160
|
+
const max = Math.min(buffer.length, tag.length - 1);
|
|
161
|
+
for (let size = max; size > 0; size -= 1) {
|
|
162
|
+
if (tag.startsWith(buffer.slice(buffer.length - size))) return size;
|
|
163
|
+
}
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
var ThinkTagSplitter = class {
|
|
167
|
+
pending = "";
|
|
168
|
+
inThink = false;
|
|
169
|
+
answer = "";
|
|
170
|
+
reasoning = "";
|
|
171
|
+
push(chunk) {
|
|
172
|
+
this.pending += chunk;
|
|
173
|
+
let answerDelta = "";
|
|
174
|
+
let reasoningDelta = "";
|
|
175
|
+
while (this.pending.length > 0) {
|
|
176
|
+
if (!this.inThink) {
|
|
177
|
+
const openAt = this.pending.indexOf(OPEN);
|
|
178
|
+
if (openAt >= 0) {
|
|
179
|
+
answerDelta += this.pending.slice(0, openAt);
|
|
180
|
+
this.pending = this.pending.slice(openAt + OPEN.length);
|
|
181
|
+
this.inThink = true;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const hold2 = holdSuffix(this.pending, OPEN);
|
|
185
|
+
answerDelta += this.pending.slice(0, this.pending.length - hold2);
|
|
186
|
+
this.pending = this.pending.slice(this.pending.length - hold2);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
const closeAt = this.pending.indexOf(CLOSE);
|
|
190
|
+
if (closeAt >= 0) {
|
|
191
|
+
reasoningDelta += this.pending.slice(0, closeAt);
|
|
192
|
+
this.pending = this.pending.slice(closeAt + CLOSE.length);
|
|
193
|
+
this.inThink = false;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const hold = holdSuffix(this.pending, CLOSE);
|
|
197
|
+
reasoningDelta += this.pending.slice(0, this.pending.length - hold);
|
|
198
|
+
this.pending = this.pending.slice(this.pending.length - hold);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
this.answer += answerDelta;
|
|
202
|
+
this.reasoning += reasoningDelta;
|
|
203
|
+
return { answerDelta, reasoningDelta };
|
|
204
|
+
}
|
|
205
|
+
finish() {
|
|
206
|
+
if (this.inThink) {
|
|
207
|
+
const reasoningDelta = this.pending;
|
|
208
|
+
this.pending = "";
|
|
209
|
+
this.reasoning += reasoningDelta;
|
|
210
|
+
return { answerDelta: "", reasoningDelta };
|
|
211
|
+
}
|
|
212
|
+
const answerDelta = this.pending;
|
|
213
|
+
this.pending = "";
|
|
214
|
+
this.answer += answerDelta;
|
|
215
|
+
return { answerDelta, reasoningDelta: "" };
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
function splitThinkTags(text) {
|
|
219
|
+
const splitter = new ThinkTagSplitter();
|
|
220
|
+
splitter.push(text);
|
|
221
|
+
splitter.finish();
|
|
222
|
+
return { answer: splitter.answer, reasoning: splitter.reasoning };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/apply-resolution.ts
|
|
226
|
+
function applyResolution(response, prepared) {
|
|
227
|
+
const result = response;
|
|
228
|
+
if (prepared.resolution) result.reasoningResolution = prepared.resolution;
|
|
229
|
+
const directive = prepared.directive;
|
|
230
|
+
if (!directive) return result;
|
|
231
|
+
const raw = response.raw?.response;
|
|
232
|
+
const pathText = firstConcatenatedText(raw, directive.response.reasoningTextPaths);
|
|
233
|
+
let tagText;
|
|
234
|
+
if (directive.response.stripThinkTags) {
|
|
235
|
+
const split = splitThinkTags(result.text);
|
|
236
|
+
result.text = split.answer;
|
|
237
|
+
if (split.reasoning) tagText = split.reasoning;
|
|
238
|
+
}
|
|
239
|
+
const reasoningText = pathText ?? tagText;
|
|
240
|
+
if (reasoningText) result.reasoningText = reasoningText;
|
|
241
|
+
applyUsage(result, raw, directive);
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
function applyUsage(response, raw, directive) {
|
|
245
|
+
const paths = directive.usage?.reasoningTokenPaths;
|
|
246
|
+
if (!paths || paths.length === 0) return;
|
|
247
|
+
const tokens = readReasoningTokens(raw, paths);
|
|
248
|
+
if (tokens === void 0) {
|
|
249
|
+
if (!response.usage || response.usage.reasoningTokens === void 0) return;
|
|
250
|
+
const usage = { ...response.usage };
|
|
251
|
+
delete usage.reasoningTokens;
|
|
252
|
+
response.usage = usage;
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
response.usage = { ...response.usage ?? {}, reasoningTokens: tokens };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/decorate-stream.ts
|
|
259
|
+
function isRecord3(value) {
|
|
260
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
261
|
+
}
|
|
262
|
+
async function* decorateStream(prepared, source) {
|
|
263
|
+
const splitter = prepared.directive?.response.stripThinkTags ? new ThinkTagSplitter() : void 0;
|
|
264
|
+
let reasoningText = "";
|
|
265
|
+
for await (const event of source) {
|
|
266
|
+
if (event.type === "stream.start") {
|
|
267
|
+
yield withResolution(event, prepared);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (event.type === "stream.reasoning.delta") {
|
|
271
|
+
const text = textOf(event);
|
|
272
|
+
if (text) reasoningText += text;
|
|
273
|
+
yield event;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (event.type === "stream.text.delta" && splitter) {
|
|
277
|
+
const pushed = splitter.push(textOf(event));
|
|
278
|
+
if (pushed.reasoningDelta) {
|
|
279
|
+
reasoningText += pushed.reasoningDelta;
|
|
280
|
+
yield {
|
|
281
|
+
type: "stream.reasoning.delta",
|
|
282
|
+
requestId: event.requestId,
|
|
283
|
+
data: { text: pushed.reasoningDelta }
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if (pushed.answerDelta) {
|
|
287
|
+
yield { ...event, data: { text: pushed.answerDelta } };
|
|
288
|
+
}
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (event.type === "stream.usage") {
|
|
292
|
+
yield adjustUsageEvent(event, prepared);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (event.type === "stream.done") {
|
|
296
|
+
if (splitter) {
|
|
297
|
+
const finished = splitter.finish();
|
|
298
|
+
if (finished.reasoningDelta) {
|
|
299
|
+
reasoningText += finished.reasoningDelta;
|
|
300
|
+
yield {
|
|
301
|
+
type: "stream.reasoning.delta",
|
|
302
|
+
requestId: event.requestId,
|
|
303
|
+
data: { text: finished.reasoningDelta }
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (finished.answerDelta) {
|
|
307
|
+
yield {
|
|
308
|
+
type: "stream.text.delta",
|
|
309
|
+
requestId: event.requestId,
|
|
310
|
+
data: { text: finished.answerDelta }
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const data = isRecord3(event.data) ? { ...event.data } : {};
|
|
315
|
+
if (splitter) data.text = splitter.answer;
|
|
316
|
+
if (reasoningText) data.reasoningText = reasoningText;
|
|
317
|
+
if (prepared.resolution) data.reasoningResolution = prepared.resolution;
|
|
318
|
+
yield { ...event, data };
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
yield event;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function withResolution(event, prepared) {
|
|
325
|
+
if (!prepared.resolution) return event;
|
|
326
|
+
const data = isRecord3(event.data) ? { ...event.data } : {};
|
|
327
|
+
data.reasoningResolution = prepared.resolution;
|
|
328
|
+
return { ...event, data };
|
|
329
|
+
}
|
|
330
|
+
function textOf(event) {
|
|
331
|
+
if (!isRecord3(event.data) || typeof event.data.text !== "string") return "";
|
|
332
|
+
return event.data.text;
|
|
333
|
+
}
|
|
334
|
+
function adjustUsageEvent(event, prepared) {
|
|
335
|
+
const paths = prepared.directive?.usage?.reasoningTokenPaths;
|
|
336
|
+
if (!paths || paths.length === 0 || !isRecord3(event.data) || !isRecord3(event.data.usage)) return event;
|
|
337
|
+
const usage = { ...event.data.usage };
|
|
338
|
+
const tokens = readReasoningTokens(usage.raw, paths);
|
|
339
|
+
if (tokens === void 0) {
|
|
340
|
+
delete usage.reasoningTokens;
|
|
341
|
+
} else {
|
|
342
|
+
usage.reasoningTokens = tokens;
|
|
343
|
+
}
|
|
344
|
+
return { ...event, data: { ...event.data, usage } };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/types/providers.ts
|
|
348
|
+
var IMPLEMENTED_AI_PROVIDERS = ["openrouter", "bedrock", "openai"];
|
|
349
|
+
function isImplementedAiProvider(provider) {
|
|
350
|
+
return IMPLEMENTED_AI_PROVIDERS.includes(provider);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/errors.ts
|
|
354
|
+
var AiDispatcherError = class extends Error {
|
|
355
|
+
constructor(code, message, details) {
|
|
356
|
+
super(message);
|
|
357
|
+
this.code = code;
|
|
358
|
+
this.details = details;
|
|
359
|
+
this.name = "AiDispatcherError";
|
|
360
|
+
}
|
|
361
|
+
code;
|
|
362
|
+
details;
|
|
363
|
+
};
|
|
364
|
+
function providerNotImplemented(provider) {
|
|
365
|
+
const implemented = [...IMPLEMENTED_AI_PROVIDERS];
|
|
366
|
+
return new AiDispatcherError(
|
|
367
|
+
"PROVIDER_NOT_IMPLEMENTED",
|
|
368
|
+
`AI provider "${provider}" is not implemented yet. Supported by @x12i/ai-dispatcher: ${implemented.map((id) => `"${id}"`).join(", ")}.`,
|
|
369
|
+
{
|
|
370
|
+
provider,
|
|
371
|
+
implementedProviders: implemented,
|
|
372
|
+
operatorHint: `Pass provider: "${implemented[0]}" (or omit provider to use the default).`
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/mcp.ts
|
|
378
|
+
var TOOL_TOKEN = /^[A-Za-z0-9_-]+$/;
|
|
379
|
+
var MAX_EXPOSED_NAME_LENGTH = 64;
|
|
380
|
+
var DEFAULT_PARAMETERS = { type: "object", properties: {} };
|
|
381
|
+
function createMcpCatalog(mcp) {
|
|
382
|
+
if (mcp === void 0) return { servers: [], tools: [] };
|
|
383
|
+
if (!Array.isArray(mcp.servers)) {
|
|
384
|
+
throw new AiDispatcherError("MCP_SERVER_INVALID", "mcp.servers must be an array.");
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
servers: mcp.servers,
|
|
388
|
+
tools: indexMcpServers(mcp.servers)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function indexMcpServers(servers) {
|
|
392
|
+
const seenServers = /* @__PURE__ */ new Set();
|
|
393
|
+
const seenTools = /* @__PURE__ */ new Set();
|
|
394
|
+
const indexed = [];
|
|
395
|
+
for (const server of servers) {
|
|
396
|
+
assertServer(server, seenServers);
|
|
397
|
+
for (const tool of server.tools) {
|
|
398
|
+
if (!isRecord4(tool)) {
|
|
399
|
+
throw new AiDispatcherError(
|
|
400
|
+
"MCP_TOOL_NAME_INVALID",
|
|
401
|
+
`MCP server "${server.id}" has a tool that is not an object.`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
const exposedName = exposedToolName(server.id, tool);
|
|
405
|
+
if (seenTools.has(exposedName)) {
|
|
406
|
+
throw new AiDispatcherError(
|
|
407
|
+
"MCP_TOOL_NAME_CONFLICT",
|
|
408
|
+
`MCP tool "${exposedName}" is defined more than once.`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
seenTools.add(exposedName);
|
|
412
|
+
indexed.push({
|
|
413
|
+
exposedName,
|
|
414
|
+
rawName: tool.name,
|
|
415
|
+
description: toolDescription(tool),
|
|
416
|
+
parameters: toolParameters(tool),
|
|
417
|
+
server
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return indexed;
|
|
422
|
+
}
|
|
423
|
+
async function refreshMcpCatalog(catalog) {
|
|
424
|
+
const listed = /* @__PURE__ */ new Map();
|
|
425
|
+
for (const server of catalog.servers) {
|
|
426
|
+
if (!server.listTools) continue;
|
|
427
|
+
let tools;
|
|
428
|
+
try {
|
|
429
|
+
tools = await server.listTools();
|
|
430
|
+
} catch (error) {
|
|
431
|
+
throw listFailed(server.id, error);
|
|
432
|
+
}
|
|
433
|
+
if (!Array.isArray(tools)) {
|
|
434
|
+
throw new AiDispatcherError(
|
|
435
|
+
"MCP_LIST_FAILED",
|
|
436
|
+
`MCP server "${server.id}" listTools did not return an array.`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
listed.set(server.id, tools);
|
|
440
|
+
}
|
|
441
|
+
const draft = catalog.servers.map((server) => {
|
|
442
|
+
const tools = listed.get(server.id);
|
|
443
|
+
return tools ? { ...server, tools } : server;
|
|
444
|
+
});
|
|
445
|
+
try {
|
|
446
|
+
indexMcpServers(draft);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
throw new AiDispatcherError(
|
|
449
|
+
"MCP_LIST_FAILED",
|
|
450
|
+
error instanceof Error ? error.message : "MCP tool list is invalid."
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
for (const server of catalog.servers) {
|
|
454
|
+
const tools = listed.get(server.id);
|
|
455
|
+
if (tools) server.tools = tools;
|
|
456
|
+
}
|
|
457
|
+
catalog.tools = indexMcpServers(catalog.servers);
|
|
458
|
+
}
|
|
459
|
+
function applyMcpCatalog(params) {
|
|
460
|
+
const filter = params.request.mcp;
|
|
461
|
+
delete params.request.mcp;
|
|
462
|
+
if (!params.includeMcp || filter === false || Array.isArray(filter) && filter.length === 0) return;
|
|
463
|
+
const allowed = resolveAllowlist(filter, params.catalog.tools);
|
|
464
|
+
const requestTools = params.request.functionTools ?? [];
|
|
465
|
+
const requestNames = new Set(requestTools.map((tool) => tool.name));
|
|
466
|
+
const injected = [];
|
|
467
|
+
for (const entry of params.catalog.tools) {
|
|
468
|
+
if (allowed && !allowed.has(entry.exposedName)) continue;
|
|
469
|
+
if (requestNames.has(entry.exposedName)) continue;
|
|
470
|
+
const clientExecutor = params.providerTools?.[entry.exposedName];
|
|
471
|
+
injected.push({
|
|
472
|
+
name: entry.exposedName,
|
|
473
|
+
description: entry.description,
|
|
474
|
+
parameters: entry.parameters,
|
|
475
|
+
...typeof clientExecutor === "function" ? {} : { executor: createMcpExecutor(entry.server, entry.rawName) }
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
if (injected.length) {
|
|
479
|
+
params.request.functionTools = [...injected, ...requestTools];
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function createMcpExecutor(server, rawName) {
|
|
483
|
+
return async (args, context) => {
|
|
484
|
+
const result = await server.callTool(rawName, toArgs(args), context);
|
|
485
|
+
return flattenMcpResult(result);
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function flattenMcpResult(result) {
|
|
489
|
+
if (typeof result === "string") return result;
|
|
490
|
+
if (!isRecord4(result)) return stringify(result);
|
|
491
|
+
const text = textContent(result.content);
|
|
492
|
+
if (result.isError === true) {
|
|
493
|
+
throw new Error(text ?? stringify(result));
|
|
494
|
+
}
|
|
495
|
+
if (text !== void 0) return text;
|
|
496
|
+
return stringify(result);
|
|
497
|
+
}
|
|
498
|
+
function resolveAllowlist(filter, tools) {
|
|
499
|
+
if (filter === void 0) return void 0;
|
|
500
|
+
if (!Array.isArray(filter) || filter.some((name) => typeof name !== "string")) {
|
|
501
|
+
throw new AiDispatcherError(
|
|
502
|
+
"MCP_TOOL_NOT_FOUND",
|
|
503
|
+
"request.mcp must be false or a list of exposed tool names."
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
const known = new Set(tools.map((tool) => tool.exposedName));
|
|
507
|
+
for (const name of filter) {
|
|
508
|
+
if (!known.has(name)) {
|
|
509
|
+
throw new AiDispatcherError("MCP_TOOL_NOT_FOUND", `MCP tool "${name}" is not registered.`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return new Set(filter);
|
|
513
|
+
}
|
|
514
|
+
function assertServer(server, seen) {
|
|
515
|
+
if (!isRecord4(server) || typeof server.id !== "string" || server.id.length === 0 || !Array.isArray(server.tools) || typeof server.callTool !== "function") {
|
|
516
|
+
throw new AiDispatcherError(
|
|
517
|
+
"MCP_SERVER_INVALID",
|
|
518
|
+
"An MCP server needs an id, a tools array, and a callTool function."
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
if (seen.has(server.id)) {
|
|
522
|
+
throw new AiDispatcherError("MCP_SERVER_INVALID", `MCP server "${server.id}" is listed more than once.`);
|
|
523
|
+
}
|
|
524
|
+
seen.add(server.id);
|
|
525
|
+
if (!TOOL_TOKEN.test(server.id)) {
|
|
526
|
+
throw new AiDispatcherError(
|
|
527
|
+
"MCP_TOOL_NAME_INVALID",
|
|
528
|
+
`MCP server id "${server.id}" must use letters, numbers, underscores, and hyphens.`
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function exposedToolName(serverId, tool) {
|
|
533
|
+
if (typeof tool.name !== "string" || !TOOL_TOKEN.test(serverId) || !TOOL_TOKEN.test(tool.name)) {
|
|
534
|
+
throw new AiDispatcherError(
|
|
535
|
+
"MCP_TOOL_NAME_INVALID",
|
|
536
|
+
`MCP tool "${serverId}.${typeof tool.name === "string" ? tool.name : ""}" must use letters, numbers, underscores, and hyphens.`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
const exposedName = `${serverId}__${tool.name}`;
|
|
540
|
+
if (exposedName.length > MAX_EXPOSED_NAME_LENGTH) {
|
|
541
|
+
throw new AiDispatcherError(
|
|
542
|
+
"MCP_TOOL_NAME_INVALID",
|
|
543
|
+
`MCP tool "${exposedName}" is longer than ${MAX_EXPOSED_NAME_LENGTH} characters.`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
return exposedName;
|
|
547
|
+
}
|
|
548
|
+
function toolDescription(tool) {
|
|
549
|
+
if (tool.description === void 0) return "";
|
|
550
|
+
if (typeof tool.description !== "string") {
|
|
551
|
+
throw new AiDispatcherError(
|
|
552
|
+
"MCP_TOOL_NAME_INVALID",
|
|
553
|
+
`MCP tool "${tool.name}" description must be a string.`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return tool.description;
|
|
557
|
+
}
|
|
558
|
+
function toolParameters(tool) {
|
|
559
|
+
if (tool.inputSchema === void 0) return { ...DEFAULT_PARAMETERS, properties: {} };
|
|
560
|
+
if (!isRecord4(tool.inputSchema)) {
|
|
561
|
+
throw new AiDispatcherError(
|
|
562
|
+
"MCP_TOOL_NAME_INVALID",
|
|
563
|
+
`MCP tool "${tool.name}" inputSchema must be an object.`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
return tool.inputSchema;
|
|
567
|
+
}
|
|
568
|
+
function textContent(content) {
|
|
569
|
+
if (!Array.isArray(content)) return void 0;
|
|
570
|
+
const parts = [];
|
|
571
|
+
for (const part of content) {
|
|
572
|
+
if (!isRecord4(part) || part.type !== "text" || typeof part.text !== "string") continue;
|
|
573
|
+
parts.push(part.text);
|
|
574
|
+
}
|
|
575
|
+
if (!parts.length) return void 0;
|
|
576
|
+
return parts.join("\n");
|
|
577
|
+
}
|
|
578
|
+
function toArgs(args) {
|
|
579
|
+
if (isRecord4(args)) return args;
|
|
580
|
+
return {};
|
|
581
|
+
}
|
|
582
|
+
function listFailed(serverId, error) {
|
|
583
|
+
if (error instanceof AiDispatcherError && error.code === "MCP_LIST_FAILED") return error;
|
|
584
|
+
const message = error instanceof Error ? error.message : `MCP server "${serverId}" listTools failed.`;
|
|
585
|
+
return new AiDispatcherError("MCP_LIST_FAILED", message);
|
|
586
|
+
}
|
|
587
|
+
function stringify(result) {
|
|
588
|
+
const serialized = JSON.stringify(result);
|
|
589
|
+
return serialized === void 0 ? "null" : serialized;
|
|
590
|
+
}
|
|
591
|
+
function isRecord4(value) {
|
|
592
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/prepare-request.ts
|
|
596
|
+
var import_ai_profiles = require("@x12i/ai-profiles");
|
|
597
|
+
function prepareDispatchRequest(params) {
|
|
598
|
+
const cloned = cloneRequest(params.request);
|
|
599
|
+
const provider = params.provider;
|
|
600
|
+
const reasoningEffort = cloned.reasoningEffort;
|
|
601
|
+
const bedrockControls = cloned.bedrockControls;
|
|
602
|
+
delete cloned.provider;
|
|
603
|
+
delete cloned.reasoningEffort;
|
|
604
|
+
delete cloned.bedrockControls;
|
|
605
|
+
if (provider === "openai") assertOpenAiConfigured(params.options);
|
|
606
|
+
if (provider === "bedrock" && cloned.reasoning !== void 0) {
|
|
607
|
+
if (bedrockControls?.additionalModelRequestFields === void 0 && reasoningEffort === void 0) {
|
|
608
|
+
throw new AiDispatcherError(
|
|
609
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
610
|
+
"Bedrock does not accept an OpenRouter reasoning object. Set reasoningEffort, or set bedrockControls.additionalModelRequestFields."
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
delete cloned.reasoning;
|
|
614
|
+
}
|
|
615
|
+
const requestedModel = cloned.model ?? defaultModelFor(provider, params.options) ?? "";
|
|
616
|
+
const explicit = hasExplicitControl(provider, cloned, bedrockControls);
|
|
617
|
+
let directive;
|
|
618
|
+
if (!explicit && reasoningEffort !== void 0) {
|
|
619
|
+
if (!requestedModel) {
|
|
620
|
+
throw new AiDispatcherError(
|
|
621
|
+
"REASONING_MODEL_REQUIRED",
|
|
622
|
+
"reasoningEffort requires a model id. Set request.model or the provider defaultModel."
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
directive = resolveCatalog({
|
|
626
|
+
target: provider,
|
|
627
|
+
model: requestedModel,
|
|
628
|
+
effort: reasoningEffort
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
if (provider === "openrouter") {
|
|
632
|
+
applyNestedReasoning(cloned, reasoningEffort);
|
|
633
|
+
}
|
|
634
|
+
if (directive?.model) cloned.model = directive.model;
|
|
635
|
+
if (directive?.promptPrefix) applyPromptPrefix(cloned, directive.promptPrefix);
|
|
636
|
+
const effectiveModel = cloned.model ?? requestedModel;
|
|
637
|
+
const call = {};
|
|
638
|
+
if (reasoningEffort !== void 0 && provider === "openrouter") {
|
|
639
|
+
call.suppressNestedReasoningFallback = true;
|
|
640
|
+
}
|
|
641
|
+
const overlay = directive && hasOwnKeys(directive.body) ? directive.body : explicit && provider === "bedrock" && bedrockControls?.additionalModelRequestFields ? { additionalModelRequestFields: bedrockControls.additionalModelRequestFields } : void 0;
|
|
642
|
+
if (overlay) {
|
|
643
|
+
const bodyOverlay = overlay;
|
|
644
|
+
call.finalizeBody = (body) => (0, import_ai_profiles.mergeReasoningBody)(body, bodyOverlay);
|
|
645
|
+
}
|
|
646
|
+
if (directive && hasStreamMetadata(directive)) {
|
|
647
|
+
const response = directive.response;
|
|
648
|
+
call.extractStreamReasoning = (event) => extractStreamReasoningText(event, response);
|
|
649
|
+
}
|
|
650
|
+
const resolution = reasoningEffort !== void 0 || explicit ? buildResolution({
|
|
651
|
+
requestedEffort: reasoningEffort,
|
|
652
|
+
target: provider,
|
|
653
|
+
requestedModel,
|
|
654
|
+
effectiveModel,
|
|
655
|
+
bypassed: explicit,
|
|
656
|
+
outcome: explicit ? void 0 : directive?.outcome
|
|
657
|
+
}) : void 0;
|
|
658
|
+
return {
|
|
659
|
+
provider,
|
|
660
|
+
request: cloned,
|
|
661
|
+
requestedModel,
|
|
662
|
+
effectiveModel,
|
|
663
|
+
...resolution ? { resolution } : {},
|
|
664
|
+
...directive ? { directive } : {},
|
|
665
|
+
call
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function resolveCatalog(input) {
|
|
669
|
+
try {
|
|
670
|
+
return (0, import_ai_profiles.resolveReasoning)(input);
|
|
671
|
+
} catch (error) {
|
|
672
|
+
if (error instanceof import_ai_profiles.AIProfilesError) {
|
|
673
|
+
throw new AiDispatcherError(error.code, error.message, error.details);
|
|
674
|
+
}
|
|
675
|
+
throw error;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function hasExplicitControl(provider, request, bedrockControls) {
|
|
679
|
+
if (provider === "bedrock") return bedrockControls?.additionalModelRequestFields !== void 0;
|
|
680
|
+
return request.reasoning !== void 0;
|
|
681
|
+
}
|
|
682
|
+
function buildResolution(params) {
|
|
683
|
+
return {
|
|
684
|
+
target: params.target,
|
|
685
|
+
requestedModel: params.requestedModel,
|
|
686
|
+
effectiveModel: params.effectiveModel,
|
|
687
|
+
bypassed: params.bypassed,
|
|
688
|
+
...params.requestedEffort ? { requestedEffort: params.requestedEffort } : {},
|
|
689
|
+
...params.outcome ? { outcome: params.outcome } : {}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function applyNestedReasoning(request, parentEffort) {
|
|
693
|
+
const serverTools = request.serverTools;
|
|
694
|
+
if (!serverTools) return;
|
|
695
|
+
if (serverTools.fusion) serverTools.fusion = settleNested(serverTools.fusion, "fusion", parentEffort);
|
|
696
|
+
if (serverTools.advisor) {
|
|
697
|
+
serverTools.advisor = Array.isArray(serverTools.advisor) ? serverTools.advisor.map((advisor) => settleNested(advisor, "advisor", parentEffort)) : settleNested(serverTools.advisor, "advisor", parentEffort);
|
|
698
|
+
}
|
|
699
|
+
if (serverTools.subagent) serverTools.subagent = settleNested(serverTools.subagent, "subagent", parentEffort);
|
|
700
|
+
}
|
|
701
|
+
function settleNested(policy, kind, parentEffort) {
|
|
702
|
+
const current = policy;
|
|
703
|
+
const childEffort = current.reasoningEffort;
|
|
704
|
+
const next = { ...current };
|
|
705
|
+
delete next.reasoningEffort;
|
|
706
|
+
if (next.reasoning !== void 0) return next;
|
|
707
|
+
const effort = childEffort ?? parentEffort;
|
|
708
|
+
if (!effort) return next;
|
|
709
|
+
const model = kind === "fusion" ? next.judgeModel : next.model;
|
|
710
|
+
if (!model) return next;
|
|
711
|
+
const directive = resolveCatalog({ target: "openrouter", model, effort });
|
|
712
|
+
if (directive.model) {
|
|
713
|
+
if (kind === "fusion") next.judgeModel = directive.model;
|
|
714
|
+
else next.model = directive.model;
|
|
715
|
+
}
|
|
716
|
+
if (isRecord5(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
|
|
717
|
+
return next;
|
|
718
|
+
}
|
|
719
|
+
function applyPromptPrefix(request, prefix) {
|
|
720
|
+
if (typeof request.prompt === "string") {
|
|
721
|
+
request.prompt = prefix + request.prompt;
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (prefixMessages(request.messages, prefix)) return;
|
|
725
|
+
if (typeof request.input === "string") {
|
|
726
|
+
request.input = prefix + request.input;
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (Array.isArray(request.input) && prefixInputItems(request.input, prefix)) return;
|
|
730
|
+
throw new AiDispatcherError(
|
|
731
|
+
"REASONING_PREFIX_UNSUPPORTED",
|
|
732
|
+
"The catalog prompt prefix cannot be applied because the request has no user text to prefix."
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
function prefixMessages(messages, prefix) {
|
|
736
|
+
if (!messages) return false;
|
|
737
|
+
for (const message of messages) {
|
|
738
|
+
if (message.role !== "user") continue;
|
|
739
|
+
if (typeof message.content === "string") {
|
|
740
|
+
message.content = prefix + message.content;
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
for (const part of message.content) {
|
|
744
|
+
if (part.type === "text") {
|
|
745
|
+
part.text = prefix + part.text;
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
function prefixInputItems(items, prefix) {
|
|
753
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
754
|
+
const item = items[index];
|
|
755
|
+
if (typeof item === "string") {
|
|
756
|
+
items[index] = prefix + item;
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
if (!item || item.role === "system") continue;
|
|
760
|
+
if (typeof item.content === "string" && (item.role === "user" || item.role === void 0)) {
|
|
761
|
+
item.content = prefix + item.content;
|
|
762
|
+
return true;
|
|
763
|
+
}
|
|
764
|
+
if (!Array.isArray(item.content)) continue;
|
|
765
|
+
for (const part of item.content) {
|
|
766
|
+
if (!isRecord5(part) || typeof part.text !== "string") continue;
|
|
767
|
+
if (part.type === "text" || part.type === "input_text") {
|
|
768
|
+
part.text = prefix + part.text;
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
function assertOpenAiConfigured(options) {
|
|
776
|
+
const apiKey = options.openai?.apiKey ?? readEnv("OPENAI_API_KEY");
|
|
777
|
+
if (!apiKey) {
|
|
778
|
+
throw new AiDispatcherError(
|
|
779
|
+
"OPENAI_API_KEY_MISSING",
|
|
780
|
+
"OpenAI API key is required. Set openai.apiKey or OPENAI_API_KEY."
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function defaultModelFor(provider, options) {
|
|
785
|
+
if (provider === "openrouter") return options.openrouter?.defaultModel;
|
|
786
|
+
if (provider === "bedrock") return options.bedrock?.defaultModel;
|
|
787
|
+
return options.openai?.defaultModel;
|
|
788
|
+
}
|
|
789
|
+
function cloneRequest(request) {
|
|
790
|
+
const functionTools = request.functionTools?.map((tool) => ({ ...tool }));
|
|
791
|
+
const cloned = JSON.parse(JSON.stringify(request));
|
|
792
|
+
if (functionTools) cloned.functionTools = functionTools;
|
|
793
|
+
return cloned;
|
|
794
|
+
}
|
|
795
|
+
function hasOwnKeys(value) {
|
|
796
|
+
return Object.keys(value).length > 0;
|
|
797
|
+
}
|
|
798
|
+
function hasStreamMetadata(directive) {
|
|
799
|
+
return directive.response.streamReasoningKeys.length > 0 || directive.response.streamReasoningPaths.length > 0;
|
|
800
|
+
}
|
|
801
|
+
function isRecord5(value) {
|
|
802
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
803
|
+
}
|
|
804
|
+
function readEnv(name) {
|
|
805
|
+
if (typeof process === "undefined") return void 0;
|
|
806
|
+
return process.env[name];
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/providers/bedrock.ts
|
|
810
|
+
var import_bedrock_runtime = require("@x12i/bedrock-runtime");
|
|
811
|
+
function createBedrockProviderAdapter(options = {}) {
|
|
812
|
+
const runtime = (0, import_bedrock_runtime.createBedrockRuntime)(options);
|
|
813
|
+
return {
|
|
814
|
+
run(request, call) {
|
|
815
|
+
return runtime.run(toBedrockRequest(request), toCall(call));
|
|
816
|
+
},
|
|
817
|
+
executeStreamingChat(request, call) {
|
|
818
|
+
return runtime.executeStreamingChat(
|
|
819
|
+
toBedrockRequest(request),
|
|
820
|
+
toCall(call)
|
|
821
|
+
);
|
|
822
|
+
},
|
|
823
|
+
compile(request, call) {
|
|
824
|
+
return runtime.compile(toBedrockRequest(request), toCall(call));
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
function toCall(call) {
|
|
829
|
+
if (!call) return void 0;
|
|
830
|
+
return {
|
|
831
|
+
...call.finalizeBody ? { finalizeBody: call.finalizeBody } : {},
|
|
832
|
+
...call.extractStreamReasoning ? { extractStreamReasoning: call.extractStreamReasoning } : {}
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
function toBedrockRequest(request) {
|
|
836
|
+
assertBedrockSupported(request);
|
|
837
|
+
const mapped = {};
|
|
838
|
+
if (request.id !== void 0) mapped.id = request.id;
|
|
839
|
+
if (request.model !== void 0) mapped.model = request.model;
|
|
840
|
+
const hasMessages2 = Array.isArray(request.messages) && request.messages.length > 0;
|
|
841
|
+
if (hasMessages2) {
|
|
842
|
+
mapped.messages = request.messages;
|
|
843
|
+
} else if (Array.isArray(request.input)) {
|
|
844
|
+
mapped.messages = inputItemsToMessages(request.input);
|
|
845
|
+
}
|
|
846
|
+
const system = request.system?.trim() ? request.system : request.instructions;
|
|
847
|
+
if (system !== void 0) mapped.system = system;
|
|
848
|
+
if (request.prompt !== void 0) mapped.prompt = request.prompt;
|
|
849
|
+
else if (!hasMessages2 && !Array.isArray(request.input) && typeof request.input === "string") {
|
|
850
|
+
mapped.prompt = request.input;
|
|
851
|
+
}
|
|
852
|
+
if (request.temperature !== void 0) mapped.temperature = request.temperature;
|
|
853
|
+
if (request.maxTokens !== void 0) mapped.maxTokens = request.maxTokens;
|
|
854
|
+
if (request.functionTools !== void 0) {
|
|
855
|
+
mapped.functionTools = request.functionTools;
|
|
856
|
+
}
|
|
857
|
+
if (request.toolChoice !== void 0 && isBedrockToolChoice(request.toolChoice)) {
|
|
858
|
+
mapped.toolChoice = request.toolChoice;
|
|
859
|
+
}
|
|
860
|
+
if (request.execution !== void 0) {
|
|
861
|
+
mapped.execution = {
|
|
862
|
+
...request.execution.maxToolIterations !== void 0 ? { maxToolIterations: request.execution.maxToolIterations } : {},
|
|
863
|
+
...request.execution.maxFunctionToolCalls !== void 0 ? { maxFunctionToolCalls: request.execution.maxFunctionToolCalls } : {},
|
|
864
|
+
...request.execution.timeoutMs !== void 0 ? { timeoutMs: request.execution.timeoutMs } : {}
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
if (request.metadata !== void 0) mapped.metadata = request.metadata;
|
|
868
|
+
return mapped;
|
|
869
|
+
}
|
|
870
|
+
function assertBedrockSupported(request) {
|
|
871
|
+
if (request.serverTools && serverToolsEnabled(request.serverTools)) {
|
|
872
|
+
throw new AiDispatcherError(
|
|
873
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
874
|
+
"Bedrock does not support OpenRouter server tools."
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
if (request.responseFormat !== void 0) {
|
|
878
|
+
throw new AiDispatcherError("UNSUPPORTED_REQUEST_FIELD", "Bedrock does not accept responseFormat.");
|
|
879
|
+
}
|
|
880
|
+
if (request.rawOpenRouterOverrides !== void 0) {
|
|
881
|
+
throw new AiDispatcherError(
|
|
882
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
883
|
+
"rawOpenRouterOverrides cannot be sent to Bedrock."
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
if (request.apiMode !== void 0) {
|
|
887
|
+
throw new AiDispatcherError("UNSUPPORTED_REQUEST_FIELD", "Bedrock does not accept apiMode.");
|
|
888
|
+
}
|
|
889
|
+
if (request.toolChoice && typeof request.toolChoice === "object" && request.toolChoice.type === "server_tool") {
|
|
890
|
+
throw new AiDispatcherError(
|
|
891
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
892
|
+
"Bedrock does not support OpenRouter server tool choices."
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
if (hasMessages(request) && Array.isArray(request.input)) {
|
|
896
|
+
throw new AiDispatcherError(
|
|
897
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
898
|
+
"Bedrock accepts messages or input items, not both."
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function hasMessages(request) {
|
|
903
|
+
return Array.isArray(request.messages) && request.messages.length > 0;
|
|
904
|
+
}
|
|
905
|
+
function serverToolsEnabled(policy) {
|
|
906
|
+
return Object.values(policy).some((value) => {
|
|
907
|
+
if (Array.isArray(value)) return value.some((item) => isEnabledMode(item));
|
|
908
|
+
return isEnabledMode(value);
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
function isEnabledMode(value) {
|
|
912
|
+
return typeof value === "object" && value !== null && "mode" in value && value.mode !== "disabled";
|
|
913
|
+
}
|
|
914
|
+
function inputItemsToMessages(items) {
|
|
915
|
+
return items.map((item) => {
|
|
916
|
+
if (typeof item === "string") return { role: "user", content: item };
|
|
917
|
+
const role = item.role;
|
|
918
|
+
if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool" || !hasContent(item.content)) {
|
|
919
|
+
throw new AiDispatcherError(
|
|
920
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
921
|
+
"Bedrock input items must be text or messages with role and content."
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
role,
|
|
926
|
+
content: item.content,
|
|
927
|
+
...typeof item.name === "string" ? { name: item.name } : {},
|
|
928
|
+
...typeof item.toolCallId === "string" ? { toolCallId: item.toolCallId } : {}
|
|
929
|
+
};
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
function hasContent(content) {
|
|
933
|
+
return typeof content === "string" || Array.isArray(content);
|
|
934
|
+
}
|
|
935
|
+
function isBedrockToolChoice(toolChoice) {
|
|
936
|
+
if (toolChoice === void 0) return false;
|
|
937
|
+
if (toolChoice === "auto" || toolChoice === "none" || toolChoice === "required") return true;
|
|
938
|
+
if (typeof toolChoice === "object" && toolChoice.type === "function") return true;
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/providers/openai.ts
|
|
943
|
+
var DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
944
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
945
|
+
function createOpenAiProviderAdapter(options = {}) {
|
|
946
|
+
const resolved = resolveOpenAiOptions(options);
|
|
947
|
+
return {
|
|
948
|
+
compile(request, call) {
|
|
949
|
+
return compileOpenAiRequest(request, resolved, call, false);
|
|
950
|
+
},
|
|
951
|
+
async run(request, call) {
|
|
952
|
+
const requestId = request.id ?? "openai";
|
|
953
|
+
try {
|
|
954
|
+
return await runOpenAi(request, resolved, call, requestId);
|
|
955
|
+
} catch (error) {
|
|
956
|
+
if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
|
|
957
|
+
return failedResponse(request, requestId, error);
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
executeStreamingChat(request, call) {
|
|
961
|
+
return streamOpenAi(request, resolved, call);
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
function compileOpenAiRequest(request, options, call, streaming) {
|
|
966
|
+
assertSupported(request);
|
|
967
|
+
const model = (request.model ?? options.defaultModel ?? "").trim();
|
|
968
|
+
if (!model) {
|
|
969
|
+
throw new AiDispatcherError(
|
|
970
|
+
"MODEL_REQUIRED",
|
|
971
|
+
"A model id is required. Set request.model or openai.defaultModel."
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
if (!options.apiKey) {
|
|
975
|
+
throw new AiDispatcherError(
|
|
976
|
+
"OPENAI_API_KEY_MISSING",
|
|
977
|
+
"OpenAI API key is required. Set openai.apiKey or OPENAI_API_KEY."
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
const instructions = compileInstructions(request);
|
|
981
|
+
const input = compileInput(request);
|
|
982
|
+
if (input === void 0) {
|
|
983
|
+
throw new AiDispatcherError("INPUT_REQUIRED", "Provide request.messages, request.input, or request.prompt.");
|
|
984
|
+
}
|
|
985
|
+
let body = clean({
|
|
986
|
+
model,
|
|
987
|
+
input,
|
|
988
|
+
instructions,
|
|
989
|
+
temperature: request.temperature,
|
|
990
|
+
max_output_tokens: request.maxTokens,
|
|
991
|
+
reasoning: request.reasoning,
|
|
992
|
+
text: request.responseFormat,
|
|
993
|
+
tools: compileTools(request),
|
|
994
|
+
tool_choice: compileToolChoice(request)
|
|
995
|
+
});
|
|
996
|
+
if (call?.finalizeBody) body = call.finalizeBody(body);
|
|
997
|
+
if (streaming) body = { ...body, stream: true };
|
|
998
|
+
else body = { ...body, stream: false };
|
|
999
|
+
return {
|
|
1000
|
+
url: `${options.baseUrl}/responses`,
|
|
1001
|
+
headers: buildHeaders(options),
|
|
1002
|
+
body,
|
|
1003
|
+
warnings: []
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function assertSupported(request) {
|
|
1007
|
+
if (request.serverTools && serverToolsEnabled2(request.serverTools)) {
|
|
1008
|
+
throw new AiDispatcherError(
|
|
1009
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1010
|
+
"Direct OpenAI does not support OpenRouter server tools."
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
if (request.rawOpenRouterOverrides !== void 0) {
|
|
1014
|
+
throw new AiDispatcherError(
|
|
1015
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1016
|
+
"rawOpenRouterOverrides cannot be sent to the OpenAI Responses API."
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
if (request.apiMode === "chat") {
|
|
1020
|
+
throw new AiDispatcherError(
|
|
1021
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1022
|
+
"Direct OpenAI uses the Responses API. Do not set apiMode to chat."
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
if (request.toolChoice && typeof request.toolChoice === "object" && request.toolChoice.type === "server_tool") {
|
|
1026
|
+
throw new AiDispatcherError(
|
|
1027
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1028
|
+
"Direct OpenAI does not support OpenRouter server tool choices."
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
function serverToolsEnabled2(policy) {
|
|
1033
|
+
return Object.values(policy).some((value) => {
|
|
1034
|
+
if (Array.isArray(value)) return value.some((item) => isEnabledMode2(item));
|
|
1035
|
+
return isEnabledMode2(value);
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
function isEnabledMode2(value) {
|
|
1039
|
+
return typeof value === "object" && value !== null && "mode" in value && value.mode !== "disabled";
|
|
1040
|
+
}
|
|
1041
|
+
function compileInstructions(request) {
|
|
1042
|
+
if (request.instructions) return request.instructions;
|
|
1043
|
+
if (request.system) return request.system;
|
|
1044
|
+
const systemMessages = (request.messages ?? []).filter((message) => message.role === "system");
|
|
1045
|
+
if (!systemMessages.length) return void 0;
|
|
1046
|
+
return systemMessages.map((message) => contentToText(message.content)).filter(Boolean).join("\n");
|
|
1047
|
+
}
|
|
1048
|
+
function compileInput(request) {
|
|
1049
|
+
if (request.input !== void 0) return request.input;
|
|
1050
|
+
const items = [];
|
|
1051
|
+
for (const message of request.messages ?? []) {
|
|
1052
|
+
if (message.role === "system" && !request.instructions && !request.system) continue;
|
|
1053
|
+
if (message.role === "system") continue;
|
|
1054
|
+
items.push(compileInputItem(message));
|
|
1055
|
+
}
|
|
1056
|
+
if (typeof request.prompt === "string") {
|
|
1057
|
+
items.push({ role: "user", content: request.prompt });
|
|
1058
|
+
}
|
|
1059
|
+
return items.length ? items : void 0;
|
|
1060
|
+
}
|
|
1061
|
+
function compileInputItem(message) {
|
|
1062
|
+
if (message.role === "tool") {
|
|
1063
|
+
return {
|
|
1064
|
+
type: "function_call_output",
|
|
1065
|
+
call_id: message.toolCallId ?? message.name ?? "tool",
|
|
1066
|
+
output: contentToText(message.content)
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
const role = message.role === "assistant" ? "assistant" : "user";
|
|
1070
|
+
return { role, content: compileContent(message.content) };
|
|
1071
|
+
}
|
|
1072
|
+
function compileContent(content) {
|
|
1073
|
+
if (typeof content === "string") return content;
|
|
1074
|
+
return content.map((part) => {
|
|
1075
|
+
if (part.type === "text") return { type: "input_text", text: part.text };
|
|
1076
|
+
if (part.type === "image_url") return { type: "input_image", image_url: part.imageUrl };
|
|
1077
|
+
if (part.url) {
|
|
1078
|
+
return clean({ type: "input_file", file_url: part.url, filename: part.fileName });
|
|
1079
|
+
}
|
|
1080
|
+
if (part.data) {
|
|
1081
|
+
return clean({ type: "input_file", file_data: part.data, filename: part.fileName });
|
|
1082
|
+
}
|
|
1083
|
+
throw new AiDispatcherError(
|
|
1084
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1085
|
+
"File content parts need a url or data."
|
|
1086
|
+
);
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
function compileTools(request) {
|
|
1090
|
+
const tools = request.functionTools ?? [];
|
|
1091
|
+
if (!tools.length) return void 0;
|
|
1092
|
+
return tools.map((tool) => ({
|
|
1093
|
+
type: "function",
|
|
1094
|
+
name: tool.name,
|
|
1095
|
+
description: tool.description,
|
|
1096
|
+
parameters: tool.parameters
|
|
1097
|
+
}));
|
|
1098
|
+
}
|
|
1099
|
+
function compileToolChoice(request) {
|
|
1100
|
+
const choice = request.toolChoice;
|
|
1101
|
+
if (!choice || choice === "auto") return void 0;
|
|
1102
|
+
if (choice === "none" || choice === "required") return choice;
|
|
1103
|
+
if (typeof choice === "object" && choice.type === "function") {
|
|
1104
|
+
return { type: "function", name: choice.functionName };
|
|
1105
|
+
}
|
|
1106
|
+
return void 0;
|
|
1107
|
+
}
|
|
1108
|
+
async function runOpenAi(request, options, call, requestId) {
|
|
1109
|
+
const maxIterations = request.execution?.maxToolIterations ?? 8;
|
|
1110
|
+
const maxFunctionToolCalls = request.execution?.maxFunctionToolCalls ?? 20;
|
|
1111
|
+
const functionUsage = [];
|
|
1112
|
+
let active = compileOpenAiRequest(request, options, call, false);
|
|
1113
|
+
let response;
|
|
1114
|
+
for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
|
|
1115
|
+
response = await sendWithRetries(active, options, timeoutFor(request, options));
|
|
1116
|
+
const calls = functionCalls(isRecord6(response) ? response : {});
|
|
1117
|
+
if (!calls.length) break;
|
|
1118
|
+
if (!calls.every((item) => resolveExecutor(item.name, request, options))) {
|
|
1119
|
+
return normalizeOpenAiResponse({
|
|
1120
|
+
request,
|
|
1121
|
+
requestId,
|
|
1122
|
+
compiled: active,
|
|
1123
|
+
response,
|
|
1124
|
+
functionTools: [...functionUsage, ...calls],
|
|
1125
|
+
status: "requires_action"
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
if (functionUsage.length + calls.length > maxFunctionToolCalls) {
|
|
1129
|
+
throw new AiDispatcherError("FUNCTION_TOOL_LIMIT", "maxFunctionToolCalls exceeded.");
|
|
1130
|
+
}
|
|
1131
|
+
const results = [];
|
|
1132
|
+
for (const item of calls) {
|
|
1133
|
+
results.push(await executeOpenAiTool(item, request, options));
|
|
1134
|
+
}
|
|
1135
|
+
functionUsage.push(...results);
|
|
1136
|
+
const record = isRecord6(response) ? response : {};
|
|
1137
|
+
active = {
|
|
1138
|
+
...active,
|
|
1139
|
+
body: clean({
|
|
1140
|
+
...active.body,
|
|
1141
|
+
tool_choice: "auto",
|
|
1142
|
+
previous_response_id: typeof record.id === "string" ? record.id : void 0,
|
|
1143
|
+
input: results.map(toFunctionCallOutput)
|
|
1144
|
+
})
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
return normalizeOpenAiResponse({
|
|
1148
|
+
request,
|
|
1149
|
+
requestId,
|
|
1150
|
+
compiled: active,
|
|
1151
|
+
response,
|
|
1152
|
+
functionTools: functionUsage,
|
|
1153
|
+
status: "completed"
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
function resolveExecutor(name, request, options) {
|
|
1157
|
+
return request.functionTools?.find((tool) => tool.name === name)?.executor ?? options.tools?.[name];
|
|
1158
|
+
}
|
|
1159
|
+
async function executeOpenAiTool(call, request, options) {
|
|
1160
|
+
const executor = resolveExecutor(call.name, request, options);
|
|
1161
|
+
if (!executor) {
|
|
1162
|
+
return call;
|
|
1163
|
+
}
|
|
1164
|
+
const started = Date.now();
|
|
1165
|
+
try {
|
|
1166
|
+
options.logger?.debug?.("runtime.function_tool.started", { name: call.name, callId: call.callId });
|
|
1167
|
+
const result = await executor(call.args, {
|
|
1168
|
+
requestId: request.id ?? "",
|
|
1169
|
+
toolName: call.name,
|
|
1170
|
+
callId: call.callId,
|
|
1171
|
+
...request.metadata ? { metadata: request.metadata } : {}
|
|
1172
|
+
});
|
|
1173
|
+
const usage = {
|
|
1174
|
+
name: call.name,
|
|
1175
|
+
callId: call.callId,
|
|
1176
|
+
status: "completed",
|
|
1177
|
+
args: call.args,
|
|
1178
|
+
result,
|
|
1179
|
+
durationMs: Date.now() - started
|
|
1180
|
+
};
|
|
1181
|
+
options.logger?.debug?.("runtime.function_tool.completed", { name: call.name, callId: call.callId });
|
|
1182
|
+
return usage;
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
const usage = {
|
|
1185
|
+
name: call.name,
|
|
1186
|
+
callId: call.callId,
|
|
1187
|
+
status: "failed",
|
|
1188
|
+
args: call.args,
|
|
1189
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1190
|
+
durationMs: Date.now() - started
|
|
1191
|
+
};
|
|
1192
|
+
options.logger?.warn?.("runtime.function_tool.failed", usage);
|
|
1193
|
+
return usage;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
function toFunctionCallOutput(result) {
|
|
1197
|
+
const output = result.status === "completed" ? result.result : { error: result.error };
|
|
1198
|
+
return {
|
|
1199
|
+
type: "function_call_output",
|
|
1200
|
+
call_id: result.callId,
|
|
1201
|
+
output: typeof output === "string" ? output : JSON.stringify(output)
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
function contentToText(content) {
|
|
1205
|
+
if (typeof content === "string") return content;
|
|
1206
|
+
return content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("");
|
|
1207
|
+
}
|
|
1208
|
+
async function* streamOpenAi(request, options, call) {
|
|
1209
|
+
const requestId = request.id ?? "";
|
|
1210
|
+
let compiled;
|
|
1211
|
+
try {
|
|
1212
|
+
compiled = compileOpenAiRequest(request, options, call, true);
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
yield errorEvent(requestId, error);
|
|
1215
|
+
throw error;
|
|
1216
|
+
}
|
|
1217
|
+
yield {
|
|
1218
|
+
type: "stream.start",
|
|
1219
|
+
requestId,
|
|
1220
|
+
data: {
|
|
1221
|
+
model: typeof compiled.body.model === "string" ? compiled.body.model : "",
|
|
1222
|
+
apiMode: "responses",
|
|
1223
|
+
entrypoint: "executeStreamingChat"
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
const controller = new AbortController();
|
|
1227
|
+
const timeout = setTimeout(() => controller.abort(), timeoutFor(request, options));
|
|
1228
|
+
let text = "";
|
|
1229
|
+
let reported = false;
|
|
1230
|
+
const report = (error) => {
|
|
1231
|
+
const failure = error instanceof AiDispatcherError ? error : new AiDispatcherError(
|
|
1232
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
1233
|
+
error instanceof Error ? error.message : "OpenAI request failed before receiving a response."
|
|
1234
|
+
);
|
|
1235
|
+
return failure;
|
|
1236
|
+
};
|
|
1237
|
+
try {
|
|
1238
|
+
const response = await options.fetch(compiled.url, {
|
|
1239
|
+
method: "POST",
|
|
1240
|
+
headers: { ...compiled.headers, Accept: "text/event-stream" },
|
|
1241
|
+
body: JSON.stringify(compiled.body),
|
|
1242
|
+
signal: controller.signal
|
|
1243
|
+
});
|
|
1244
|
+
if (!response.ok) {
|
|
1245
|
+
const failure = await readFailure(response);
|
|
1246
|
+
reported = true;
|
|
1247
|
+
yield errorEvent(requestId, failure);
|
|
1248
|
+
throw failure;
|
|
1249
|
+
}
|
|
1250
|
+
for await (const event of parseSse(response)) {
|
|
1251
|
+
if (!isRecord6(event)) continue;
|
|
1252
|
+
if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) {
|
|
1253
|
+
text += event.delta;
|
|
1254
|
+
yield { type: "stream.text.delta", requestId, data: { text: event.delta } };
|
|
1255
|
+
} else if (event.type === "response.reasoning_summary_text.delta" && call?.extractStreamReasoning) {
|
|
1256
|
+
const reasoningText = call.extractStreamReasoning(event);
|
|
1257
|
+
if (reasoningText) {
|
|
1258
|
+
yield { type: "stream.reasoning.delta", requestId, data: { text: reasoningText } };
|
|
1259
|
+
}
|
|
1260
|
+
} else if (event.type === "response.function_call_arguments.delta" && typeof event.delta === "string") {
|
|
1261
|
+
yield {
|
|
1262
|
+
type: "stream.tool_call.delta",
|
|
1263
|
+
requestId,
|
|
1264
|
+
data: {
|
|
1265
|
+
index: 0,
|
|
1266
|
+
...typeof event.name === "string" ? { name: event.name } : {},
|
|
1267
|
+
argumentsDelta: event.delta
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
} else if (event.type === "response.completed" && isRecord6(event.response)) {
|
|
1271
|
+
const usage = normalizeUsage(event.response.usage);
|
|
1272
|
+
if (usage) yield { type: "stream.usage", requestId, data: { usage } };
|
|
1273
|
+
} else if (event.type === "error") {
|
|
1274
|
+
const failure = new AiDispatcherError(
|
|
1275
|
+
"PROVIDER_REQUEST_FAILED",
|
|
1276
|
+
typeof event.message === "string" ? event.message : "OpenAI stream returned an error event.",
|
|
1277
|
+
event
|
|
1278
|
+
);
|
|
1279
|
+
reported = true;
|
|
1280
|
+
yield errorEvent(requestId, failure);
|
|
1281
|
+
throw failure;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
yield {
|
|
1285
|
+
type: "stream.done",
|
|
1286
|
+
requestId,
|
|
1287
|
+
data: {
|
|
1288
|
+
text,
|
|
1289
|
+
model: typeof compiled.body.model === "string" ? compiled.body.model : ""
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
const failure = error instanceof Error && error.name === "AbortError" ? new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "OpenAI request timed out.") : report(error);
|
|
1294
|
+
if (!reported) yield errorEvent(requestId, failure);
|
|
1295
|
+
throw failure;
|
|
1296
|
+
} finally {
|
|
1297
|
+
clearTimeout(timeout);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
async function sendWithRetries(compiled, options, timeoutMs) {
|
|
1301
|
+
const attempts = options.maxAttempts;
|
|
1302
|
+
let lastError;
|
|
1303
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
1304
|
+
try {
|
|
1305
|
+
return await sendOnce(compiled, options, timeoutMs);
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
lastError = error;
|
|
1308
|
+
const retryable = error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE";
|
|
1309
|
+
if (!retryable || attempt >= attempts) throw asTerminalError(error);
|
|
1310
|
+
await delay(Math.min(500 * 2 ** (attempt - 1), 5e3));
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
throw lastError;
|
|
1314
|
+
}
|
|
1315
|
+
async function sendOnce(compiled, options, timeoutMs) {
|
|
1316
|
+
const controller = new AbortController();
|
|
1317
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1318
|
+
try {
|
|
1319
|
+
const response = await options.fetch(compiled.url, {
|
|
1320
|
+
method: "POST",
|
|
1321
|
+
headers: compiled.headers,
|
|
1322
|
+
body: JSON.stringify(compiled.body),
|
|
1323
|
+
signal: controller.signal
|
|
1324
|
+
});
|
|
1325
|
+
if (!response.ok) throw await readFailure(response);
|
|
1326
|
+
const text = await response.text();
|
|
1327
|
+
return text ? JSON.parse(text) : {};
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
if (error instanceof AiDispatcherError) throw error;
|
|
1330
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1331
|
+
throw new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "OpenAI request timed out.");
|
|
1332
|
+
}
|
|
1333
|
+
throw new AiDispatcherError(
|
|
1334
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
1335
|
+
error instanceof Error ? error.message : "OpenAI request failed before receiving a response."
|
|
1336
|
+
);
|
|
1337
|
+
} finally {
|
|
1338
|
+
clearTimeout(timeout);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
async function readFailure(response) {
|
|
1342
|
+
const text = await response.text();
|
|
1343
|
+
let body = text;
|
|
1344
|
+
try {
|
|
1345
|
+
body = text ? JSON.parse(text) : void 0;
|
|
1346
|
+
} catch {
|
|
1347
|
+
body = text;
|
|
1348
|
+
}
|
|
1349
|
+
const message = messageFrom(body) ?? `OpenAI request failed with status ${response.status}.`;
|
|
1350
|
+
const retryable = response.status === 429 || response.status >= 500;
|
|
1351
|
+
return new AiDispatcherError(classifyStatus(response.status), message, { status: response.status, body, retryable });
|
|
1352
|
+
}
|
|
1353
|
+
function classifyStatus(status) {
|
|
1354
|
+
if (status === 401 || status === 403) return "PROVIDER_AUTH_FAILED";
|
|
1355
|
+
if (status === 404) return "PROVIDER_MODEL_NOT_FOUND";
|
|
1356
|
+
if (status === 429) return "PROVIDER_RATE_LIMITED";
|
|
1357
|
+
if (status === 408 || status >= 500) return "PROVIDER_RETRYABLE";
|
|
1358
|
+
return "PROVIDER_REQUEST_FAILED";
|
|
1359
|
+
}
|
|
1360
|
+
function isConfigError(code) {
|
|
1361
|
+
return code === "OPENAI_API_KEY_MISSING" || code === "MODEL_REQUIRED" || code === "INPUT_REQUIRED" || code === "UNSUPPORTED_REQUEST_FIELD";
|
|
1362
|
+
}
|
|
1363
|
+
function asTerminalError(error) {
|
|
1364
|
+
if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
|
|
1365
|
+
return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
|
|
1366
|
+
}
|
|
1367
|
+
if (error instanceof AiDispatcherError) return error;
|
|
1368
|
+
return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
1369
|
+
}
|
|
1370
|
+
function normalizeOpenAiResponse(params) {
|
|
1371
|
+
const record = isRecord6(params.response) ? params.response : {};
|
|
1372
|
+
const text = answerText(record);
|
|
1373
|
+
const calls = params.functionTools ?? functionCalls(record);
|
|
1374
|
+
const usage = normalizeUsage(record.usage);
|
|
1375
|
+
const status = params.status ?? (calls.length ? "requires_action" : "completed");
|
|
1376
|
+
return {
|
|
1377
|
+
id: typeof record.id === "string" ? record.id : params.requestId,
|
|
1378
|
+
status,
|
|
1379
|
+
apiMode: "responses",
|
|
1380
|
+
model: typeof record.model === "string" ? record.model : modelOf(params.compiled),
|
|
1381
|
+
text,
|
|
1382
|
+
citations: [],
|
|
1383
|
+
images: [],
|
|
1384
|
+
patches: [],
|
|
1385
|
+
toolUsage: toolUsage(calls),
|
|
1386
|
+
...usage ? { usage } : {},
|
|
1387
|
+
warnings: params.compiled.warnings,
|
|
1388
|
+
errors: [],
|
|
1389
|
+
raw: { request: params.compiled.body, response: params.response },
|
|
1390
|
+
...params.request.metadata ? { metadata: params.request.metadata } : {}
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
function answerText(response) {
|
|
1394
|
+
const output = response.output;
|
|
1395
|
+
if (Array.isArray(output)) {
|
|
1396
|
+
const parts = [];
|
|
1397
|
+
for (const item of output) {
|
|
1398
|
+
if (!isRecord6(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
1399
|
+
for (const part of item.content) {
|
|
1400
|
+
if (isRecord6(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
if (parts.length > 0) return parts.join("");
|
|
1404
|
+
}
|
|
1405
|
+
return typeof response.output_text === "string" ? response.output_text : "";
|
|
1406
|
+
}
|
|
1407
|
+
function functionCalls(response) {
|
|
1408
|
+
if (!Array.isArray(response.output)) return [];
|
|
1409
|
+
const calls = [];
|
|
1410
|
+
for (const item of response.output) {
|
|
1411
|
+
if (!isRecord6(item) || item.type !== "function_call") continue;
|
|
1412
|
+
const name = typeof item.name === "string" ? item.name : "function";
|
|
1413
|
+
const callId = typeof item.call_id === "string" ? item.call_id : name;
|
|
1414
|
+
let args = item.arguments;
|
|
1415
|
+
if (typeof item.arguments === "string") {
|
|
1416
|
+
try {
|
|
1417
|
+
args = JSON.parse(item.arguments);
|
|
1418
|
+
} catch {
|
|
1419
|
+
args = item.arguments;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
calls.push({ name, callId, status: "completed", args });
|
|
1423
|
+
}
|
|
1424
|
+
return calls;
|
|
1425
|
+
}
|
|
1426
|
+
function toolUsage(calls) {
|
|
1427
|
+
const unused = { requested: false, required: false, used: false };
|
|
1428
|
+
return {
|
|
1429
|
+
serverTools: {
|
|
1430
|
+
webSearch: unused,
|
|
1431
|
+
webFetch: unused,
|
|
1432
|
+
datetime: unused,
|
|
1433
|
+
imageGeneration: unused,
|
|
1434
|
+
applyPatch: unused,
|
|
1435
|
+
fusion: unused,
|
|
1436
|
+
advisor: unused,
|
|
1437
|
+
subagent: unused
|
|
1438
|
+
},
|
|
1439
|
+
functionTools: calls
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
function normalizeUsage(usage) {
|
|
1443
|
+
if (!isRecord6(usage)) return void 0;
|
|
1444
|
+
const inputTokens = numberValue(usage.input_tokens);
|
|
1445
|
+
const outputTokens = numberValue(usage.output_tokens);
|
|
1446
|
+
const totalTokens = numberValue(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
|
|
1447
|
+
return {
|
|
1448
|
+
raw: usage,
|
|
1449
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
1450
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
1451
|
+
...totalTokens !== void 0 ? { totalTokens } : {}
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
function failedResponse(request, requestId, error) {
|
|
1455
|
+
const normalized = error instanceof AiDispatcherError ? error : new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
1456
|
+
return {
|
|
1457
|
+
id: requestId,
|
|
1458
|
+
status: "failed",
|
|
1459
|
+
apiMode: "responses",
|
|
1460
|
+
model: request.model ?? "",
|
|
1461
|
+
text: "",
|
|
1462
|
+
citations: [],
|
|
1463
|
+
images: [],
|
|
1464
|
+
patches: [],
|
|
1465
|
+
toolUsage: toolUsage([]),
|
|
1466
|
+
warnings: [],
|
|
1467
|
+
errors: [{
|
|
1468
|
+
code: normalized.code,
|
|
1469
|
+
message: normalized.message,
|
|
1470
|
+
source: "runtime",
|
|
1471
|
+
details: normalized.details
|
|
1472
|
+
}],
|
|
1473
|
+
raw: { request, response: void 0 }
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function errorEvent(requestId, error) {
|
|
1477
|
+
const normalized = error instanceof AiDispatcherError ? error : new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
1478
|
+
return {
|
|
1479
|
+
type: "stream.error",
|
|
1480
|
+
requestId,
|
|
1481
|
+
data: {
|
|
1482
|
+
error: {
|
|
1483
|
+
code: normalized.code,
|
|
1484
|
+
message: normalized.message,
|
|
1485
|
+
source: "runtime",
|
|
1486
|
+
details: normalized.details
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
function modelOf(compiled) {
|
|
1492
|
+
return typeof compiled.body.model === "string" ? compiled.body.model : "";
|
|
1493
|
+
}
|
|
1494
|
+
function messageFrom(body) {
|
|
1495
|
+
if (!isRecord6(body)) return void 0;
|
|
1496
|
+
if (typeof body.message === "string") return body.message;
|
|
1497
|
+
if (isRecord6(body.error) && typeof body.error.message === "string") return body.error.message;
|
|
1498
|
+
return void 0;
|
|
1499
|
+
}
|
|
1500
|
+
function numberValue(value) {
|
|
1501
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1502
|
+
}
|
|
1503
|
+
function clean(body) {
|
|
1504
|
+
return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== void 0));
|
|
1505
|
+
}
|
|
1506
|
+
function buildHeaders(options) {
|
|
1507
|
+
return {
|
|
1508
|
+
"content-type": "application/json",
|
|
1509
|
+
authorization: `Bearer ${options.apiKey}`,
|
|
1510
|
+
...options.organization ? { "OpenAI-Organization": options.organization } : {},
|
|
1511
|
+
...options.project ? { "OpenAI-Project": options.project } : {}
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
function timeoutFor(request, options) {
|
|
1515
|
+
return request.execution?.timeoutMs ?? options.timeoutMs;
|
|
1516
|
+
}
|
|
1517
|
+
function delay(ms) {
|
|
1518
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1519
|
+
}
|
|
1520
|
+
async function* parseSse(response) {
|
|
1521
|
+
const body = response.body;
|
|
1522
|
+
if (!body) throw new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "OpenAI stream response had no body.");
|
|
1523
|
+
const reader = body.getReader();
|
|
1524
|
+
const decoder = new TextDecoder();
|
|
1525
|
+
let buffer = "";
|
|
1526
|
+
while (true) {
|
|
1527
|
+
const chunk = await reader.read();
|
|
1528
|
+
if (chunk.done) break;
|
|
1529
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
1530
|
+
const parts = buffer.split("\n\n");
|
|
1531
|
+
buffer = parts.pop() ?? "";
|
|
1532
|
+
for (const part of parts) {
|
|
1533
|
+
const parsed = parseSsePart(part);
|
|
1534
|
+
if (parsed !== void 0) yield parsed;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
const trailing = parseSsePart(buffer);
|
|
1538
|
+
if (trailing !== void 0) yield trailing;
|
|
1539
|
+
}
|
|
1540
|
+
function parseSsePart(part) {
|
|
1541
|
+
const data = part.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("\n");
|
|
1542
|
+
if (!data || data === "[DONE]") return void 0;
|
|
1543
|
+
try {
|
|
1544
|
+
return JSON.parse(data);
|
|
1545
|
+
} catch {
|
|
1546
|
+
return void 0;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
function resolveOpenAiOptions(options) {
|
|
1550
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1551
|
+
return {
|
|
1552
|
+
apiKey: options.apiKey ?? readEnv2("OPENAI_API_KEY") ?? "",
|
|
1553
|
+
baseUrl,
|
|
1554
|
+
...options.defaultModel ? { defaultModel: options.defaultModel } : {},
|
|
1555
|
+
...options.organization ? { organization: options.organization } : {},
|
|
1556
|
+
...options.project ? { project: options.project } : {},
|
|
1557
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
1558
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
1559
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
1560
|
+
...options.tools ? { tools: options.tools } : {},
|
|
1561
|
+
...options.logger ? { logger: options.logger } : {}
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
function readEnv2(name) {
|
|
1565
|
+
if (typeof process === "undefined") return void 0;
|
|
1566
|
+
return process.env[name];
|
|
1567
|
+
}
|
|
1568
|
+
function isRecord6(value) {
|
|
1569
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// src/providers/openrouter.ts
|
|
1573
|
+
var import_openrouter_runtime = require("@x12i/openrouter-runtime");
|
|
1574
|
+
function createOpenRouterProviderAdapter(options = {}) {
|
|
1575
|
+
const runtime = (0, import_openrouter_runtime.createOpenRouterRuntime)(options);
|
|
1576
|
+
return {
|
|
1577
|
+
run(request, call) {
|
|
1578
|
+
return runtime.run(request, toCall2(call));
|
|
1579
|
+
},
|
|
1580
|
+
executeStreamingChat(request, call) {
|
|
1581
|
+
return runtime.executeStreamingChat(request, toCall2(call));
|
|
1582
|
+
},
|
|
1583
|
+
compile(request, call) {
|
|
1584
|
+
return runtime.compile(request, toCall2(call));
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
function toCall2(call) {
|
|
1589
|
+
if (!call) return void 0;
|
|
1590
|
+
return {
|
|
1591
|
+
...call.finalizeBody ? { finalizeBody: call.finalizeBody } : {},
|
|
1592
|
+
...call.extractStreamReasoning ? { extractStreamReasoning: call.extractStreamReasoning } : {},
|
|
1593
|
+
...call.suppressNestedReasoningFallback ? { suppressNestedReasoningFallback: true } : {}
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// src/resolve-provider.ts
|
|
1598
|
+
function resolveProvider(requestProvider, defaultProvider) {
|
|
1599
|
+
const provider = requestProvider ?? defaultProvider;
|
|
1600
|
+
if (!provider || typeof provider !== "string") {
|
|
1601
|
+
throw new AiDispatcherError(
|
|
1602
|
+
"PROVIDER_REQUIRED",
|
|
1603
|
+
"An AI provider is required. Set options.provider or request.provider."
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
if (!isImplementedAiProvider(provider)) {
|
|
1607
|
+
throw providerNotImplemented(provider);
|
|
1608
|
+
}
|
|
1609
|
+
return provider;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// src/create-dispatcher.ts
|
|
1613
|
+
var SECRET_HEADERS = /* @__PURE__ */ new Set(["authorization", "x-api-key", "api-key"]);
|
|
1614
|
+
function createAiDispatcher(options = {}) {
|
|
1615
|
+
const defaultProvider = resolveProvider(options.provider, "openrouter");
|
|
1616
|
+
const catalog = createMcpCatalog(options.mcp);
|
|
1617
|
+
let refreshChain = Promise.resolve();
|
|
1618
|
+
const adapters = /* @__PURE__ */ new Map();
|
|
1619
|
+
function createAdapter(provider) {
|
|
1620
|
+
if (provider === "openrouter") {
|
|
1621
|
+
const openrouterOptions = {
|
|
1622
|
+
...options.openrouter,
|
|
1623
|
+
...options.logger && !options.openrouter?.logger ? { logger: options.logger } : {}
|
|
1624
|
+
};
|
|
1625
|
+
return createOpenRouterProviderAdapter(openrouterOptions);
|
|
1626
|
+
}
|
|
1627
|
+
if (provider === "bedrock") {
|
|
1628
|
+
const bedrockOptions = {
|
|
1629
|
+
...options.bedrock,
|
|
1630
|
+
...options.logger && !options.bedrock?.logger ? { logger: options.logger } : {}
|
|
1631
|
+
};
|
|
1632
|
+
return createBedrockProviderAdapter(bedrockOptions);
|
|
1633
|
+
}
|
|
1634
|
+
if (provider === "openai") {
|
|
1635
|
+
const openaiOptions = {
|
|
1636
|
+
...options.openai,
|
|
1637
|
+
...options.logger && !options.openai?.logger ? { logger: options.logger } : {}
|
|
1638
|
+
};
|
|
1639
|
+
return createOpenAiProviderAdapter(openaiOptions);
|
|
1640
|
+
}
|
|
1641
|
+
throw new AiDispatcherError(
|
|
1642
|
+
"PROVIDER_ADAPTER_MISSING",
|
|
1643
|
+
`No adapter registered for provider "${provider}".`
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
function getAdapter(provider) {
|
|
1647
|
+
let adapter = adapters.get(provider);
|
|
1648
|
+
if (!adapter) {
|
|
1649
|
+
adapter = createAdapter(provider);
|
|
1650
|
+
adapters.set(provider, adapter);
|
|
1651
|
+
}
|
|
1652
|
+
return adapter;
|
|
1653
|
+
}
|
|
1654
|
+
getAdapter(defaultProvider);
|
|
1655
|
+
return {
|
|
1656
|
+
provider: defaultProvider,
|
|
1657
|
+
async run(request) {
|
|
1658
|
+
const prepared = prepare(request, true);
|
|
1659
|
+
log(options, "ai-dispatcher.request.started", prepared, "run", false);
|
|
1660
|
+
const response = await getAdapter(prepared.provider).run(prepared.request, callOf(prepared.call));
|
|
1661
|
+
return applyResolution(response, prepared);
|
|
1662
|
+
},
|
|
1663
|
+
executeStreamingChat(request) {
|
|
1664
|
+
const prepared = prepare(request, false);
|
|
1665
|
+
log(options, "ai-dispatcher.request.started", prepared, "executeStreamingChat", true);
|
|
1666
|
+
return decorateStream(
|
|
1667
|
+
prepared,
|
|
1668
|
+
getAdapter(prepared.provider).executeStreamingChat(prepared.request, callOf(prepared.call))
|
|
1669
|
+
);
|
|
1670
|
+
},
|
|
1671
|
+
compile(request) {
|
|
1672
|
+
const prepared = prepare(request, true);
|
|
1673
|
+
log(options, "ai-dispatcher.request.compiled", prepared, "compile", false);
|
|
1674
|
+
const compiled = getAdapter(prepared.provider).compile(prepared.request, callOf(prepared.call));
|
|
1675
|
+
return attachCompileMeta(prepared, compiled);
|
|
1676
|
+
},
|
|
1677
|
+
refreshMcp() {
|
|
1678
|
+
const pending = refreshChain.then(() => refreshMcpCatalog(catalog));
|
|
1679
|
+
refreshChain = pending.then(
|
|
1680
|
+
() => void 0,
|
|
1681
|
+
() => void 0
|
|
1682
|
+
);
|
|
1683
|
+
return pending;
|
|
1684
|
+
}
|
|
1685
|
+
};
|
|
1686
|
+
function prepare(request, includeMcp) {
|
|
1687
|
+
const provider = resolveProvider(request.provider, defaultProvider);
|
|
1688
|
+
const prepared = prepareDispatchRequest({ request, provider, options });
|
|
1689
|
+
const tools = providerTools(provider, options);
|
|
1690
|
+
applyMcpCatalog({
|
|
1691
|
+
request: prepared.request,
|
|
1692
|
+
catalog,
|
|
1693
|
+
...tools ? { providerTools: tools } : {},
|
|
1694
|
+
includeMcp
|
|
1695
|
+
});
|
|
1696
|
+
return prepared;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
function providerTools(provider, options) {
|
|
1700
|
+
if (provider === "openrouter") return options.openrouter?.tools;
|
|
1701
|
+
if (provider === "bedrock") return options.bedrock?.tools;
|
|
1702
|
+
return options.openai?.tools;
|
|
1703
|
+
}
|
|
1704
|
+
function callOf(call) {
|
|
1705
|
+
if (!call.finalizeBody && !call.extractStreamReasoning && !call.suppressNestedReasoningFallback) return void 0;
|
|
1706
|
+
return call;
|
|
1707
|
+
}
|
|
1708
|
+
function attachCompileMeta(prepared, compiled) {
|
|
1709
|
+
const record = compiled;
|
|
1710
|
+
const headers = isRecord7(record.headers) ? redactHeaders(record.headers) : void 0;
|
|
1711
|
+
return {
|
|
1712
|
+
...record,
|
|
1713
|
+
...headers ? { headers } : {},
|
|
1714
|
+
provider: prepared.provider,
|
|
1715
|
+
requestedModel: prepared.requestedModel,
|
|
1716
|
+
effectiveModel: prepared.effectiveModel,
|
|
1717
|
+
...prepared.resolution ? { reasoningResolution: prepared.resolution } : {}
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
function redactHeaders(headers) {
|
|
1721
|
+
const redacted = {};
|
|
1722
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1723
|
+
redacted[key] = SECRET_HEADERS.has(key.toLowerCase()) ? "[redacted]" : value;
|
|
1724
|
+
}
|
|
1725
|
+
return redacted;
|
|
1726
|
+
}
|
|
1727
|
+
function log(options, event, prepared, entrypoint, streaming) {
|
|
1728
|
+
const payload = {
|
|
1729
|
+
provider: prepared.provider,
|
|
1730
|
+
entrypoint,
|
|
1731
|
+
streaming,
|
|
1732
|
+
requestedModel: prepared.requestedModel,
|
|
1733
|
+
effectiveModel: prepared.effectiveModel
|
|
1734
|
+
};
|
|
1735
|
+
if (prepared.request.id) payload.requestId = prepared.request.id;
|
|
1736
|
+
if (prepared.resolution?.outcome) payload.outcome = prepared.resolution.outcome;
|
|
1737
|
+
if (prepared.resolution) payload.bypassed = prepared.resolution.bypassed;
|
|
1738
|
+
if (event.endsWith("compiled")) options.logger?.debug?.(event, payload);
|
|
1739
|
+
else options.logger?.info?.(event, payload);
|
|
1740
|
+
}
|
|
1741
|
+
function isRecord7(value) {
|
|
1742
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/history.ts
|
|
1746
|
+
function stripReasoningHistoryFields(message, paths) {
|
|
1747
|
+
const clone = structuredClone(message);
|
|
1748
|
+
for (const path of paths) deletePointer(clone, path);
|
|
1749
|
+
return clone;
|
|
1750
|
+
}
|
|
1751
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1752
|
+
0 && (module.exports = {
|
|
1753
|
+
AiDispatcherError,
|
|
1754
|
+
IMPLEMENTED_AI_PROVIDERS,
|
|
1755
|
+
createAiDispatcher,
|
|
1756
|
+
isImplementedAiProvider,
|
|
1757
|
+
providerNotImplemented,
|
|
1758
|
+
resolveProvider,
|
|
1759
|
+
stripReasoningHistoryFields
|
|
1760
|
+
});
|
|
1761
|
+
//# sourceMappingURL=index.cjs.map
|