@microfox/ai-router 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/chunk-BJTO5JO5.mjs +11 -0
- package/dist/chunk-BJTO5JO5.mjs.map +1 -0
- package/dist/fs_store.d.mts +5 -0
- package/dist/fs_store.d.ts +5 -0
- package/dist/fs_store.js +122 -0
- package/dist/fs_store.js.map +1 -0
- package/dist/fs_store.mjs +101 -0
- package/dist/fs_store.mjs.map +1 -0
- package/dist/index.d.mts +340 -0
- package/dist/index.d.ts +340 -0
- package/dist/index.js +1164 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1122 -0
- package/dist/index.mjs.map +1 -0
- package/dist/store-BBHh-uTh.d.mts +27 -0
- package/dist/store-BBHh-uTh.d.ts +27 -0
- package/package.json +1 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,1164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AgentDefinitionMissingError: () => AgentDefinitionMissingError,
|
|
34
|
+
AgentNotFoundError: () => AgentNotFoundError,
|
|
35
|
+
AiKitError: () => AiKitError,
|
|
36
|
+
AiRouter: () => AiRouter,
|
|
37
|
+
MaxCallDepthExceededError: () => MaxCallDepthExceededError,
|
|
38
|
+
MemoryStore: () => MemoryStore,
|
|
39
|
+
StreamWriter: () => StreamWriter,
|
|
40
|
+
findFirstElement: () => findFirstElement,
|
|
41
|
+
findLastElement: () => findLastElement,
|
|
42
|
+
findLastMessageWith: () => findLastMessageWith,
|
|
43
|
+
getGlobalLogger: () => getGlobalLogger,
|
|
44
|
+
getTextParts: () => getTextParts,
|
|
45
|
+
getTextPartsContent: () => getTextPartsContent,
|
|
46
|
+
setGlobalLogger: () => setGlobalLogger
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/router.ts
|
|
51
|
+
var import_ai = require("ai");
|
|
52
|
+
|
|
53
|
+
// ../../node_modules/nanoid/index.js
|
|
54
|
+
var import_crypto = __toESM(require("crypto"), 1);
|
|
55
|
+
var POOL_SIZE_MULTIPLIER = 128;
|
|
56
|
+
var pool;
|
|
57
|
+
var poolOffset;
|
|
58
|
+
var fillPool = (bytes) => {
|
|
59
|
+
if (!pool || pool.length < bytes) {
|
|
60
|
+
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
|
|
61
|
+
import_crypto.default.randomFillSync(pool);
|
|
62
|
+
poolOffset = 0;
|
|
63
|
+
} else if (poolOffset + bytes > pool.length) {
|
|
64
|
+
import_crypto.default.randomFillSync(pool);
|
|
65
|
+
poolOffset = 0;
|
|
66
|
+
}
|
|
67
|
+
poolOffset += bytes;
|
|
68
|
+
};
|
|
69
|
+
var random = (bytes) => {
|
|
70
|
+
fillPool(bytes |= 0);
|
|
71
|
+
return pool.subarray(poolOffset - bytes, poolOffset);
|
|
72
|
+
};
|
|
73
|
+
var customRandom = (alphabet, defaultSize, getRandom) => {
|
|
74
|
+
let mask = (2 << 31 - Math.clz32(alphabet.length - 1 | 1)) - 1;
|
|
75
|
+
let step = Math.ceil(1.6 * mask * defaultSize / alphabet.length);
|
|
76
|
+
return (size = defaultSize) => {
|
|
77
|
+
let id = "";
|
|
78
|
+
while (true) {
|
|
79
|
+
let bytes = getRandom(step);
|
|
80
|
+
let i = step;
|
|
81
|
+
while (i--) {
|
|
82
|
+
id += alphabet[bytes[i] & mask] || "";
|
|
83
|
+
if (id.length === size) return id;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
var customAlphabet = (alphabet, size = 21) => customRandom(alphabet, size, random);
|
|
89
|
+
|
|
90
|
+
// src/helper.ts
|
|
91
|
+
var findLastElement = (array) => {
|
|
92
|
+
return array[array.length - 1];
|
|
93
|
+
};
|
|
94
|
+
var findFirstElement = (array) => {
|
|
95
|
+
return array[0];
|
|
96
|
+
};
|
|
97
|
+
var StreamWriter = class {
|
|
98
|
+
constructor(writer) {
|
|
99
|
+
this.generateId = () => {
|
|
100
|
+
return customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 10)();
|
|
101
|
+
};
|
|
102
|
+
this.writeMessageMetadata = (metadata) => {
|
|
103
|
+
return this.writer.write({
|
|
104
|
+
type: "message-metadata",
|
|
105
|
+
messageMetadata: metadata
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
this.writeCustomTool = (tool2) => {
|
|
109
|
+
const toolCallId = tool2.toolCallId || tool2.toolName?.toString() + "-" + this.generateId();
|
|
110
|
+
if ("input" in tool2 && tool2.input) {
|
|
111
|
+
this.writer.write({
|
|
112
|
+
type: "tool-input-available",
|
|
113
|
+
input: tool2.input,
|
|
114
|
+
toolCallId,
|
|
115
|
+
toolName: tool2.toolName
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (tool2.inputTextDelta && tool2.inputTextDelta.length > 0 || "output" in tool2 && tool2.output) {
|
|
119
|
+
this.writer.write({
|
|
120
|
+
type: "tool-input-start",
|
|
121
|
+
toolCallId,
|
|
122
|
+
toolName: tool2.toolName
|
|
123
|
+
});
|
|
124
|
+
if (tool2.inputTextDelta) {
|
|
125
|
+
for (const delta of tool2.inputTextDelta) {
|
|
126
|
+
this.writer.write({
|
|
127
|
+
type: "tool-input-delta",
|
|
128
|
+
toolCallId,
|
|
129
|
+
inputTextDelta: delta
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if ("output" in tool2 && tool2.output) {
|
|
135
|
+
this.writer.write({
|
|
136
|
+
type: "tool-output-available",
|
|
137
|
+
toolCallId,
|
|
138
|
+
output: tool2.output
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
this.writeObjectAsTool = (tool2) => {
|
|
143
|
+
if (!tool2.result?.object) {
|
|
144
|
+
throw new Error("No object found in the GenerateObjectResult");
|
|
145
|
+
}
|
|
146
|
+
const toolCallId = tool2.toolName.toString() + "-" + this.generateId();
|
|
147
|
+
this.writer.write({
|
|
148
|
+
type: "tool-input-start",
|
|
149
|
+
toolCallId,
|
|
150
|
+
toolName: tool2.toolName
|
|
151
|
+
});
|
|
152
|
+
this.writer.write({
|
|
153
|
+
type: "tool-input-available",
|
|
154
|
+
toolCallId,
|
|
155
|
+
input: tool2.input ?? tool2.result ? {
|
|
156
|
+
usage: tool2.result?.usage,
|
|
157
|
+
warnings: tool2.result?.warnings,
|
|
158
|
+
finishReason: tool2.result?.finishReason
|
|
159
|
+
} : void 0,
|
|
160
|
+
toolName: tool2.toolName
|
|
161
|
+
});
|
|
162
|
+
this.writer.write({
|
|
163
|
+
type: "tool-output-available",
|
|
164
|
+
toolCallId,
|
|
165
|
+
output: tool2.result?.object
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
this.writer = writer;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var getTextParts = (message) => {
|
|
172
|
+
if (!message) return [];
|
|
173
|
+
return message.parts.filter((part) => part.type === "text").map((part) => part.text);
|
|
174
|
+
};
|
|
175
|
+
var getTextPartsContent = (message) => {
|
|
176
|
+
if (!message) return "";
|
|
177
|
+
return message.parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
|
|
178
|
+
};
|
|
179
|
+
var findLastMessageWith = (message, filters) => {
|
|
180
|
+
if (!message) return null;
|
|
181
|
+
return message.filter((m) => {
|
|
182
|
+
if (filters.role && m.role !== filters.role) return false;
|
|
183
|
+
if (filters.metadata) {
|
|
184
|
+
for (const key in filters.metadata) {
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
}).pop();
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// src/router.ts
|
|
192
|
+
var import_zod = require("zod");
|
|
193
|
+
var import_path = __toESM(require("path"));
|
|
194
|
+
|
|
195
|
+
// src/store.ts
|
|
196
|
+
var MemoryStore = class {
|
|
197
|
+
constructor() {
|
|
198
|
+
this.store = /* @__PURE__ */ new Map();
|
|
199
|
+
}
|
|
200
|
+
async get(key) {
|
|
201
|
+
return this.store.get(key);
|
|
202
|
+
}
|
|
203
|
+
async set(key, value) {
|
|
204
|
+
this.store.set(key, value);
|
|
205
|
+
}
|
|
206
|
+
async delete(key) {
|
|
207
|
+
this.store.delete(key);
|
|
208
|
+
}
|
|
209
|
+
async has(key) {
|
|
210
|
+
return this.store.has(key);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/router.ts
|
|
215
|
+
var globalLogger = void 0;
|
|
216
|
+
function setGlobalLogger(logger) {
|
|
217
|
+
globalLogger = logger;
|
|
218
|
+
}
|
|
219
|
+
function getGlobalLogger() {
|
|
220
|
+
return globalLogger;
|
|
221
|
+
}
|
|
222
|
+
function clubParts(parts) {
|
|
223
|
+
if (!parts || parts.length === 0) return parts;
|
|
224
|
+
const clubbedParts = [];
|
|
225
|
+
const toolCallIdGroups = /* @__PURE__ */ new Map();
|
|
226
|
+
const dataIdGroups = /* @__PURE__ */ new Map();
|
|
227
|
+
for (const part of parts) {
|
|
228
|
+
if (part.type?.startsWith("tool-") && part.toolCallId) {
|
|
229
|
+
const toolCallId = part.toolCallId;
|
|
230
|
+
if (!toolCallIdGroups.has(toolCallId)) {
|
|
231
|
+
toolCallIdGroups.set(toolCallId, []);
|
|
232
|
+
}
|
|
233
|
+
toolCallIdGroups.get(toolCallId).push(part);
|
|
234
|
+
} else if (part.type?.startsWith("data-") && part.id) {
|
|
235
|
+
const id = part.id;
|
|
236
|
+
if (!dataIdGroups.has(id)) {
|
|
237
|
+
dataIdGroups.set(id, []);
|
|
238
|
+
}
|
|
239
|
+
dataIdGroups.get(id).push(part);
|
|
240
|
+
} else {
|
|
241
|
+
clubbedParts.push(part);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const [toolCallId, toolParts] of toolCallIdGroups) {
|
|
245
|
+
if (toolParts.length === 1) {
|
|
246
|
+
clubbedParts.push(toolParts[0]);
|
|
247
|
+
} else {
|
|
248
|
+
const mergedPart = { ...toolParts[0] };
|
|
249
|
+
for (let i = 1; i < toolParts.length; i++) {
|
|
250
|
+
const currentPart = toolParts[i];
|
|
251
|
+
Object.keys(currentPart).forEach((key) => {
|
|
252
|
+
if (key !== "type" && key !== "toolCallId") {
|
|
253
|
+
mergedPart[key] = currentPart[key];
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
clubbedParts.push(mergedPart);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
for (const [id, dataParts] of dataIdGroups) {
|
|
261
|
+
if (dataParts.length === 1) {
|
|
262
|
+
clubbedParts.push(dataParts[0]);
|
|
263
|
+
} else {
|
|
264
|
+
const mergedPart = { ...dataParts[0] };
|
|
265
|
+
for (let i = 1; i < dataParts.length; i++) {
|
|
266
|
+
const currentPart = dataParts[i];
|
|
267
|
+
Object.keys(currentPart).forEach((key) => {
|
|
268
|
+
if (key !== "type" && key !== "id") {
|
|
269
|
+
mergedPart[key] = currentPart[key];
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
clubbedParts.push(mergedPart);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return clubbedParts;
|
|
277
|
+
}
|
|
278
|
+
var AiKitError = class extends Error {
|
|
279
|
+
constructor(message) {
|
|
280
|
+
super(message);
|
|
281
|
+
this.name = "AiKitError";
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var AgentNotFoundError = class extends AiKitError {
|
|
285
|
+
constructor(path2) {
|
|
286
|
+
super(`[AiAgentKit] Agent not found for path: ${path2}`);
|
|
287
|
+
this.name = "AgentNotFoundError";
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var MaxCallDepthExceededError = class extends AiKitError {
|
|
291
|
+
constructor(maxDepth) {
|
|
292
|
+
super(`[AiAgentKit] Agent call depth limit (${maxDepth}) exceeded.`);
|
|
293
|
+
this.name = "MaxCallDepthExceededError";
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
var AgentDefinitionMissingError = class extends AiKitError {
|
|
297
|
+
constructor(path2) {
|
|
298
|
+
super(
|
|
299
|
+
`[AiAgentKit] agentAsTool: No definition found for "${path2}". Please define it using '.actAsTool()' or pass a definition as the second argument.`
|
|
300
|
+
);
|
|
301
|
+
this.name = "AgentDefinitionMissingError";
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
function parsePathPattern(pattern) {
|
|
305
|
+
const paramNames = [];
|
|
306
|
+
const parts = pattern.split(/(\/:[^\/]+)/);
|
|
307
|
+
const regexPattern = parts.map((part) => {
|
|
308
|
+
if (part.startsWith("/:")) {
|
|
309
|
+
paramNames.push(part.substring(2));
|
|
310
|
+
return "/([^/]+)";
|
|
311
|
+
}
|
|
312
|
+
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
313
|
+
}).join("");
|
|
314
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
315
|
+
return { regex, paramNames };
|
|
316
|
+
}
|
|
317
|
+
function extractPathParams(pattern, path2) {
|
|
318
|
+
const { regex, paramNames } = parsePathPattern(pattern);
|
|
319
|
+
const match = path2.match(regex);
|
|
320
|
+
if (!match) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
const params = {};
|
|
324
|
+
paramNames.forEach((paramName, index) => {
|
|
325
|
+
const value = match[index + 1];
|
|
326
|
+
if (value !== void 0) {
|
|
327
|
+
params[paramName] = value;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
return params;
|
|
331
|
+
}
|
|
332
|
+
var AiRouter = class _AiRouter {
|
|
333
|
+
/**
|
|
334
|
+
* Constructs a new AiAgentKit router.
|
|
335
|
+
* @param stack An optional initial stack of layers, used for composing routers.
|
|
336
|
+
* @param options Optional configuration for the router.
|
|
337
|
+
*/
|
|
338
|
+
constructor(stack, options) {
|
|
339
|
+
this.stack = [];
|
|
340
|
+
this.actAsToolDefinitions = /* @__PURE__ */ new Map();
|
|
341
|
+
this.logger = void 0;
|
|
342
|
+
this._store = new MemoryStore();
|
|
343
|
+
/** Configuration options for the router instance. */
|
|
344
|
+
this.options = {
|
|
345
|
+
maxCallDepth: 10
|
|
346
|
+
};
|
|
347
|
+
this.pendingExecutions = 0;
|
|
348
|
+
if (stack) {
|
|
349
|
+
this.stack = stack;
|
|
350
|
+
}
|
|
351
|
+
if (options?.maxCallDepth) {
|
|
352
|
+
this.options.maxCallDepth = options.maxCallDepth;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
setStore(store) {
|
|
356
|
+
this._store = store;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Sets a logger for this router instance.
|
|
360
|
+
* If no logger is set, the router will fall back to the global logger.
|
|
361
|
+
* @param logger The logger to use for this router instance, or undefined to use global logger
|
|
362
|
+
*/
|
|
363
|
+
setLogger(logger) {
|
|
364
|
+
this.logger = logger;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Gets the effective logger for this router instance.
|
|
368
|
+
* Returns instance logger if set, otherwise falls back to global logger.
|
|
369
|
+
* @returns The effective logger or undefined if no logging should occur
|
|
370
|
+
*/
|
|
371
|
+
_getEffectiveLogger() {
|
|
372
|
+
return this.logger ?? globalLogger;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Registers a middleware-style agent that runs for a specific path prefix, regex pattern, or wildcard.
|
|
376
|
+
* Agents can modify the context and must call `next()` to pass control to the next handler in the chain.
|
|
377
|
+
* This method is primarily for middleware. For terminal agents, see `.agent()` on an instance.
|
|
378
|
+
*
|
|
379
|
+
* @param path The path prefix, regex pattern, or "*" for wildcard matching.
|
|
380
|
+
* @param agents The agent middleware function(s).
|
|
381
|
+
*/
|
|
382
|
+
agent(agentPath, ...agents) {
|
|
383
|
+
let prefix = "/";
|
|
384
|
+
if (typeof agentPath === "string" || agentPath instanceof RegExp) {
|
|
385
|
+
prefix = agentPath;
|
|
386
|
+
} else {
|
|
387
|
+
agents.unshift(agentPath);
|
|
388
|
+
}
|
|
389
|
+
for (const handler of agents) {
|
|
390
|
+
if (typeof handler !== "function") {
|
|
391
|
+
if (handler instanceof _AiRouter && typeof prefix === "string") {
|
|
392
|
+
const router = handler;
|
|
393
|
+
const mountPath = prefix.toString().replace(/\/$/, "");
|
|
394
|
+
router.stack.forEach((layer) => {
|
|
395
|
+
const layerPath = layer.path.toString();
|
|
396
|
+
const relativeLayerPath = layerPath.startsWith("/") ? layerPath.substring(1) : layerPath;
|
|
397
|
+
const newPath = import_path.default.posix.join(mountPath, relativeLayerPath);
|
|
398
|
+
this.stack.push({ ...layer, path: newPath });
|
|
399
|
+
});
|
|
400
|
+
router.actAsToolDefinitions.forEach((value, key) => {
|
|
401
|
+
const keyPath = key.toString();
|
|
402
|
+
const relativeKeyPath = keyPath.startsWith("/") ? keyPath.substring(1) : keyPath;
|
|
403
|
+
const newKey = import_path.default.posix.join(mountPath, relativeKeyPath);
|
|
404
|
+
this.actAsToolDefinitions.set(newKey, value);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
this.stack.push({
|
|
410
|
+
path: prefix,
|
|
411
|
+
handler,
|
|
412
|
+
isAgent: true
|
|
413
|
+
// Mark as an agent
|
|
414
|
+
});
|
|
415
|
+
this.logger?.log(`Agent registered: path=${prefix}`);
|
|
416
|
+
}
|
|
417
|
+
return this;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Registers middleware that runs BEFORE agent execution for a specific path prefix, regex pattern, or wildcard.
|
|
421
|
+
* The middleware can modify the context and must call `next()` to pass control to the next handler.
|
|
422
|
+
*
|
|
423
|
+
* @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
|
|
424
|
+
* @param handler The middleware function or AiAgentKit router instance to mount.
|
|
425
|
+
*/
|
|
426
|
+
before(mountPathArg, handler) {
|
|
427
|
+
if (mountPathArg instanceof RegExp && handler instanceof _AiRouter) {
|
|
428
|
+
throw new AiKitError(
|
|
429
|
+
"[AiAgentKit] Mounting a router on a RegExp path is not supported."
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
if (handler instanceof _AiRouter) {
|
|
433
|
+
const router = handler;
|
|
434
|
+
const mountPath = mountPathArg.toString().replace(/\/$/, "");
|
|
435
|
+
router.stack.forEach((layer) => {
|
|
436
|
+
const layerPath = layer.path.toString();
|
|
437
|
+
const relativeLayerPath = layerPath.startsWith("/") ? layerPath.substring(1) : layerPath;
|
|
438
|
+
const newPath = import_path.default.posix.join(mountPath, relativeLayerPath);
|
|
439
|
+
this.stack.push({ ...layer, path: newPath });
|
|
440
|
+
});
|
|
441
|
+
router.actAsToolDefinitions.forEach((value, key) => {
|
|
442
|
+
const keyPath = key.toString();
|
|
443
|
+
const relativeKeyPath = keyPath.startsWith("/") ? keyPath.substring(1) : keyPath;
|
|
444
|
+
const newKey = import_path.default.posix.join(mountPath, relativeKeyPath);
|
|
445
|
+
this.actAsToolDefinitions.set(newKey, value);
|
|
446
|
+
});
|
|
447
|
+
} else {
|
|
448
|
+
this.stack.push({
|
|
449
|
+
path: mountPathArg,
|
|
450
|
+
handler,
|
|
451
|
+
isAgent: false,
|
|
452
|
+
// Middleware is not a terminal agent
|
|
453
|
+
timing: "before"
|
|
454
|
+
// Mark as before middleware
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return this;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Registers middleware that runs AFTER agent execution for a specific path prefix, regex pattern, or wildcard.
|
|
461
|
+
* The middleware can modify the context and must call `next()` to pass control to the next handler.
|
|
462
|
+
*
|
|
463
|
+
* @param mountPathArg The path prefix, regex pattern, or "*" for wildcard matching.
|
|
464
|
+
* @param handler The middleware function or AiAgentKit router instance to mount.
|
|
465
|
+
*/
|
|
466
|
+
after(mountPathArg, handler) {
|
|
467
|
+
if (mountPathArg instanceof RegExp && handler instanceof _AiRouter) {
|
|
468
|
+
throw new AiKitError(
|
|
469
|
+
"[AiAgentKit] Mounting a router on a RegExp path is not supported."
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
if (handler instanceof _AiRouter) {
|
|
473
|
+
const router = handler;
|
|
474
|
+
const mountPath = mountPathArg.toString().replace(/\/$/, "");
|
|
475
|
+
router.stack.forEach((layer) => {
|
|
476
|
+
const layerPath = layer.path.toString();
|
|
477
|
+
const relativeLayerPath = layerPath.startsWith("/") ? layerPath.substring(1) : layerPath;
|
|
478
|
+
const newPath = import_path.default.posix.join(mountPath, relativeLayerPath);
|
|
479
|
+
this.stack.push({ ...layer, path: newPath });
|
|
480
|
+
});
|
|
481
|
+
router.actAsToolDefinitions.forEach((value, key) => {
|
|
482
|
+
const keyPath = key.toString();
|
|
483
|
+
const relativeKeyPath = keyPath.startsWith("/") ? keyPath.substring(1) : keyPath;
|
|
484
|
+
const newKey = import_path.default.posix.join(mountPath, relativeKeyPath);
|
|
485
|
+
this.actAsToolDefinitions.set(newKey, value);
|
|
486
|
+
});
|
|
487
|
+
} else {
|
|
488
|
+
this.stack.push({
|
|
489
|
+
path: mountPathArg,
|
|
490
|
+
handler,
|
|
491
|
+
isAgent: false,
|
|
492
|
+
// Middleware is not a terminal agent
|
|
493
|
+
timing: "after"
|
|
494
|
+
// Mark as after middleware
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
return this;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Pre-defines the schema and description for an agent when it is used as a tool by an LLM.
|
|
501
|
+
* This allows `next.agentAsTool()` to create a valid `Tool` object without needing the definition at call time.
|
|
502
|
+
* @param path The path of the agent being defined.
|
|
503
|
+
* @param options The tool definition, including a Zod schema and description.
|
|
504
|
+
*/
|
|
505
|
+
actAsTool(path2, options) {
|
|
506
|
+
this.actAsToolDefinitions.set(path2, options);
|
|
507
|
+
this.logger?.log(`[actAsTool] Added definition: at path ${path2}`);
|
|
508
|
+
this.logger?.log(
|
|
509
|
+
`[actAsTool] Router now has ${this.actAsToolDefinitions.size} definitions`
|
|
510
|
+
);
|
|
511
|
+
return this;
|
|
512
|
+
}
|
|
513
|
+
getToolSet() {
|
|
514
|
+
let allTools = Array.from(this.actAsToolDefinitions.entries()).map(
|
|
515
|
+
([key, value]) => {
|
|
516
|
+
return {
|
|
517
|
+
...value,
|
|
518
|
+
metadata: {
|
|
519
|
+
...value.metadata,
|
|
520
|
+
absolutePath: key
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
);
|
|
525
|
+
return allTools.reduce((acc, _tool) => {
|
|
526
|
+
const { inputSchema, outputSchema } = _tool;
|
|
527
|
+
acc[_tool.id] = {
|
|
528
|
+
...(0, import_ai.tool)(
|
|
529
|
+
_tool
|
|
530
|
+
),
|
|
531
|
+
metadata: {
|
|
532
|
+
..._tool.metadata,
|
|
533
|
+
toolKey: _tool.id,
|
|
534
|
+
name: _tool.name,
|
|
535
|
+
description: _tool.description
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
return acc;
|
|
539
|
+
}, {});
|
|
540
|
+
}
|
|
541
|
+
getToolDefinition(path2) {
|
|
542
|
+
let definition = this.actAsToolDefinitions.get(path2);
|
|
543
|
+
if (!definition) {
|
|
544
|
+
this.logger?.error(
|
|
545
|
+
`[getToolDefinition] No definition found for path: ${path2}`
|
|
546
|
+
);
|
|
547
|
+
throw new AgentDefinitionMissingError(path2);
|
|
548
|
+
}
|
|
549
|
+
return definition;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Outputs all registered paths, and the middlewares and agents registered on each path.
|
|
553
|
+
* @returns A map of paths to their registered handlers.
|
|
554
|
+
*/
|
|
555
|
+
registry() {
|
|
556
|
+
const registryMap = {};
|
|
557
|
+
for (const layer of this.stack) {
|
|
558
|
+
const pathKey = layer.path.toString();
|
|
559
|
+
if (!registryMap[pathKey]) {
|
|
560
|
+
registryMap[pathKey] = { before: [], agents: [], after: [] };
|
|
561
|
+
}
|
|
562
|
+
if (layer.isAgent) {
|
|
563
|
+
const agentInfo = {
|
|
564
|
+
handler: layer.handler.name || "anonymous"
|
|
565
|
+
};
|
|
566
|
+
const actAsToolDef = this.actAsToolDefinitions.get(layer.path);
|
|
567
|
+
if (actAsToolDef) {
|
|
568
|
+
agentInfo.actAsTool = {
|
|
569
|
+
...actAsToolDef
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
registryMap[pathKey].agents.push(agentInfo);
|
|
573
|
+
} else {
|
|
574
|
+
const middlewareInfo = {
|
|
575
|
+
handler: layer.handler.name || "anonymous",
|
|
576
|
+
timing: layer.timing || "middleware"
|
|
577
|
+
};
|
|
578
|
+
if (layer.timing === "before") {
|
|
579
|
+
registryMap[pathKey].before.push(middlewareInfo);
|
|
580
|
+
} else if (layer.timing === "after") {
|
|
581
|
+
registryMap[pathKey].after.push(middlewareInfo);
|
|
582
|
+
} else {
|
|
583
|
+
registryMap[pathKey].before.push(middlewareInfo);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
map: registryMap,
|
|
589
|
+
tools: this.getToolSet()
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Resolves a path based on the parent path and the requested path.
|
|
594
|
+
* - If path starts with `@/`, it's an absolute path from the root.
|
|
595
|
+
* - Otherwise, it's a relative path.
|
|
596
|
+
* @internal
|
|
597
|
+
*/
|
|
598
|
+
_resolvePath(parentPath, newPath) {
|
|
599
|
+
if (newPath.startsWith("@/")) {
|
|
600
|
+
return import_path.default.posix.normalize(newPath.substring(1));
|
|
601
|
+
}
|
|
602
|
+
const joinedPath = import_path.default.posix.join(parentPath, newPath);
|
|
603
|
+
return joinedPath;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Creates a new context for an internal agent or tool call.
|
|
607
|
+
* It inherits from the parent context but gets a new logger and call depth.
|
|
608
|
+
* @internal
|
|
609
|
+
*/
|
|
610
|
+
_createSubContext(parentCtx, options) {
|
|
611
|
+
const parentDepth = parentCtx.executionContext.callDepth ?? 0;
|
|
612
|
+
const newCallDepth = parentDepth + (options.type === "agent" ? 1 : 0);
|
|
613
|
+
const subContext = {
|
|
614
|
+
...parentCtx,
|
|
615
|
+
// State is passed by reference to allow sub-agents to modify the parent's state.
|
|
616
|
+
// The execution context is a shallow copy to ensure call-specific data is isolated.
|
|
617
|
+
state: parentCtx.state,
|
|
618
|
+
store: parentCtx.store,
|
|
619
|
+
executionContext: {
|
|
620
|
+
...parentCtx.executionContext,
|
|
621
|
+
currentPath: options.path,
|
|
622
|
+
callDepth: newCallDepth
|
|
623
|
+
},
|
|
624
|
+
request: {
|
|
625
|
+
...parentCtx.request,
|
|
626
|
+
messages: options.messages || parentCtx.request.messages || [],
|
|
627
|
+
params: options.params,
|
|
628
|
+
path: options.path
|
|
629
|
+
// The path to execute
|
|
630
|
+
},
|
|
631
|
+
logger: this._createLogger(
|
|
632
|
+
parentCtx.requestId,
|
|
633
|
+
options.path,
|
|
634
|
+
newCallDepth
|
|
635
|
+
),
|
|
636
|
+
next: void 0
|
|
637
|
+
// Will be replaced right after
|
|
638
|
+
};
|
|
639
|
+
subContext.executionContext.currentPath = options.path;
|
|
640
|
+
subContext.next = new NextHandler(
|
|
641
|
+
subContext,
|
|
642
|
+
this,
|
|
643
|
+
parentCtx._onExecutionStart,
|
|
644
|
+
parentCtx._onExecutionEnd,
|
|
645
|
+
parentCtx.next
|
|
646
|
+
);
|
|
647
|
+
return subContext;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Creates a new logger instance with a structured prefix.
|
|
651
|
+
* @internal
|
|
652
|
+
*/
|
|
653
|
+
_createLogger(requestId, path2, callDepth = 0) {
|
|
654
|
+
const effectiveLogger = this._getEffectiveLogger();
|
|
655
|
+
if (!effectiveLogger) {
|
|
656
|
+
return {
|
|
657
|
+
log: () => {
|
|
658
|
+
},
|
|
659
|
+
warn: () => {
|
|
660
|
+
},
|
|
661
|
+
error: () => {
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
const indent = " ".repeat(callDepth);
|
|
666
|
+
const prefix = `${indent}[${path2.toString()}]`;
|
|
667
|
+
const fullPrefix = `[${requestId}]${prefix}`;
|
|
668
|
+
return {
|
|
669
|
+
log: (...args) => effectiveLogger.log(fullPrefix, ...args),
|
|
670
|
+
warn: (...args) => effectiveLogger.warn(fullPrefix, ...args),
|
|
671
|
+
error: (...args) => effectiveLogger.error(fullPrefix, ...args)
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Calculates a specificity score for a layer to enable Express-style routing.
|
|
676
|
+
* Higher score means more specific.
|
|
677
|
+
* - Middleware is less specific than an agent/tool.
|
|
678
|
+
* - Deeper paths are more specific.
|
|
679
|
+
* - Static segments are more specific than dynamic segments.
|
|
680
|
+
* @internal
|
|
681
|
+
*/
|
|
682
|
+
_getSpecificityScore(layer) {
|
|
683
|
+
const path2 = layer.path.toString();
|
|
684
|
+
let score = 0;
|
|
685
|
+
score += path2.split("/").length * 100;
|
|
686
|
+
score -= (path2.match(/:/g) || []).length * 10;
|
|
687
|
+
if (layer.path instanceof RegExp) {
|
|
688
|
+
score -= 50;
|
|
689
|
+
}
|
|
690
|
+
if (layer.isAgent) {
|
|
691
|
+
score += 1;
|
|
692
|
+
}
|
|
693
|
+
return score;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* The core execution engine. It finds all matching layers for a given path
|
|
697
|
+
* and runs them in a middleware-style chain.
|
|
698
|
+
* @internal
|
|
699
|
+
*/
|
|
700
|
+
async _execute(path2, ctx, isInternalCall = false) {
|
|
701
|
+
try {
|
|
702
|
+
const normalizedPath = path2.length > 1 && path2.endsWith("/") ? path2.slice(0, -1) : path2;
|
|
703
|
+
ctx.logger.log(`Executing path. isInternalCall=${isInternalCall}`);
|
|
704
|
+
const layersToRun = this.stack.filter((layer) => {
|
|
705
|
+
let shouldRun = false;
|
|
706
|
+
if (layer.path instanceof RegExp) {
|
|
707
|
+
if (isInternalCall) {
|
|
708
|
+
const exactRegex = new RegExp(`^${layer.path.source}$`);
|
|
709
|
+
shouldRun = exactRegex.test(normalizedPath);
|
|
710
|
+
} else {
|
|
711
|
+
shouldRun = layer.path.test(normalizedPath);
|
|
712
|
+
}
|
|
713
|
+
} else if (typeof layer.path === "string") {
|
|
714
|
+
const layerPath = layer.path;
|
|
715
|
+
if (layerPath === "*") {
|
|
716
|
+
return !isInternalCall;
|
|
717
|
+
}
|
|
718
|
+
const normalizedLayerPath = layerPath.length > 1 && layerPath.endsWith("/") ? layerPath.slice(0, -1) : layerPath;
|
|
719
|
+
const isExactMatch = normalizedPath === normalizedLayerPath;
|
|
720
|
+
if (isInternalCall) {
|
|
721
|
+
shouldRun = isExactMatch;
|
|
722
|
+
} else {
|
|
723
|
+
if (layer.isAgent) {
|
|
724
|
+
shouldRun = isExactMatch;
|
|
725
|
+
} else {
|
|
726
|
+
shouldRun = normalizedPath.startsWith(normalizedLayerPath);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (shouldRun) {
|
|
731
|
+
ctx.logger.log(
|
|
732
|
+
`[AiAgentKit][_execute] Layer MATCH: path=${normalizedPath}, layer.path=${layer.path}, isAgent=${layer.isAgent}, isInternal=${isInternalCall}`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
return shouldRun;
|
|
736
|
+
});
|
|
737
|
+
const beforeLayers = layersToRun.filter(
|
|
738
|
+
(l) => !l.isAgent && l.timing === "before"
|
|
739
|
+
);
|
|
740
|
+
const agentLayers = layersToRun.filter((l) => l.isAgent);
|
|
741
|
+
const afterLayers = layersToRun.filter(
|
|
742
|
+
(l) => !l.isAgent && l.timing === "after"
|
|
743
|
+
);
|
|
744
|
+
beforeLayers.sort(
|
|
745
|
+
(a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
|
|
746
|
+
);
|
|
747
|
+
agentLayers.sort(
|
|
748
|
+
(a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
|
|
749
|
+
);
|
|
750
|
+
afterLayers.sort(
|
|
751
|
+
(a, b) => this._getSpecificityScore(a) - this._getSpecificityScore(b)
|
|
752
|
+
);
|
|
753
|
+
const orderedLayers = [...beforeLayers, ...agentLayers, ...afterLayers];
|
|
754
|
+
const layerDescriptions = orderedLayers.map((l) => {
|
|
755
|
+
const type = l.isAgent ? "agent" : l.timing === "before" ? "before" : l.timing === "after" ? "after" : "middleware";
|
|
756
|
+
return `${l.path.toString()} (${type})`;
|
|
757
|
+
});
|
|
758
|
+
ctx.logger.log(
|
|
759
|
+
`Found ${orderedLayers.length} layers to run: [${layerDescriptions.join(
|
|
760
|
+
", "
|
|
761
|
+
)}]`
|
|
762
|
+
);
|
|
763
|
+
if (!agentLayers.length && !beforeLayers.length && !afterLayers.length) {
|
|
764
|
+
const errorMsg = `No agent or tool found for path: ${normalizedPath}`;
|
|
765
|
+
ctx.logger.error(errorMsg);
|
|
766
|
+
throw new AgentNotFoundError(normalizedPath);
|
|
767
|
+
}
|
|
768
|
+
const dispatch = async (index) => {
|
|
769
|
+
const layer = orderedLayers[index];
|
|
770
|
+
if (!layer) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const next = () => dispatch(index + 1);
|
|
774
|
+
const layerPath = typeof layer.path === "string" ? layer.path : layer.path.toString();
|
|
775
|
+
const layerType = layer.isAgent ? "agent" : layer.timing === "before" ? "before" : layer.timing === "after" ? "after" : "middleware";
|
|
776
|
+
ctx.logger.log(`-> Running ${layerType}: ${layerPath}`);
|
|
777
|
+
try {
|
|
778
|
+
if (ctx._onExecutionStart) {
|
|
779
|
+
ctx._onExecutionStart();
|
|
780
|
+
}
|
|
781
|
+
const result = await layer.handler(ctx, next);
|
|
782
|
+
ctx.logger.log(`<- Finished ${layerType}: ${layerPath}`);
|
|
783
|
+
return result;
|
|
784
|
+
} catch (err) {
|
|
785
|
+
ctx.logger.error(
|
|
786
|
+
`Error in ${layerType} layer for path: ${layerPath}`,
|
|
787
|
+
err
|
|
788
|
+
);
|
|
789
|
+
throw err;
|
|
790
|
+
} finally {
|
|
791
|
+
if (ctx._onExecutionEnd) {
|
|
792
|
+
ctx._onExecutionEnd();
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
return await dispatch(0);
|
|
797
|
+
} finally {
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* The main public entry point for the router. It handles an incoming request,
|
|
802
|
+
* sets up the response stream, creates the root context, and starts the execution chain.
|
|
803
|
+
*
|
|
804
|
+
* @param path The path of the agent or tool to execute.
|
|
805
|
+
* @param initialContext The initial context for the request, typically containing messages.
|
|
806
|
+
* @returns A standard `Response` object containing the rich UI stream.
|
|
807
|
+
*/
|
|
808
|
+
handle(path2, initialContext) {
|
|
809
|
+
this.logger?.log(`Handling request for path: ${path2}`);
|
|
810
|
+
const self = this;
|
|
811
|
+
let executionCompletionResolver = null;
|
|
812
|
+
const executionCompletionPromise = new Promise((resolve) => {
|
|
813
|
+
executionCompletionResolver = resolve;
|
|
814
|
+
});
|
|
815
|
+
return (0, import_ai.createUIMessageStreamResponse)({
|
|
816
|
+
stream: self.handleStream(
|
|
817
|
+
path2,
|
|
818
|
+
initialContext,
|
|
819
|
+
executionCompletionPromise,
|
|
820
|
+
executionCompletionResolver
|
|
821
|
+
)
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
handleStream(path2, initialContext, executionCompletionPromise, executionCompletionResolver) {
|
|
825
|
+
const self = this;
|
|
826
|
+
return (0, import_ai.createUIMessageStream)({
|
|
827
|
+
originalMessages: initialContext.request.messages,
|
|
828
|
+
execute: async ({ writer }) => {
|
|
829
|
+
const streamWriter = new StreamWriter(writer);
|
|
830
|
+
const requestId = (0, import_ai.generateId)();
|
|
831
|
+
const store = self._store instanceof MemoryStore ? new MemoryStore() : self._store;
|
|
832
|
+
const ctx = {
|
|
833
|
+
...initialContext,
|
|
834
|
+
request: {
|
|
835
|
+
...initialContext.request,
|
|
836
|
+
path: path2
|
|
837
|
+
// Set the initial path for the root context
|
|
838
|
+
},
|
|
839
|
+
state: {},
|
|
840
|
+
store,
|
|
841
|
+
executionContext: { currentPath: path2, callDepth: 0 },
|
|
842
|
+
requestId,
|
|
843
|
+
logger: self._createLogger(requestId, path2, 0),
|
|
844
|
+
response: {
|
|
845
|
+
...streamWriter.writer,
|
|
846
|
+
writeMessageMetadata: streamWriter.writeMessageMetadata,
|
|
847
|
+
writeCustomTool: streamWriter.writeCustomTool,
|
|
848
|
+
writeObjectAsTool: streamWriter.writeObjectAsTool,
|
|
849
|
+
generateId: import_ai.generateId
|
|
850
|
+
},
|
|
851
|
+
next: void 0,
|
|
852
|
+
// Will be replaced right after
|
|
853
|
+
_onExecutionStart: () => {
|
|
854
|
+
self.pendingExecutions++;
|
|
855
|
+
self.logger?.log(
|
|
856
|
+
`[AiAgentKit][lifecycle] Execution started. Pending: ${self.pendingExecutions}`
|
|
857
|
+
);
|
|
858
|
+
},
|
|
859
|
+
_onExecutionEnd: () => {
|
|
860
|
+
self.pendingExecutions--;
|
|
861
|
+
self.logger?.log(
|
|
862
|
+
`[AiAgentKit][lifecycle] Execution ended. Pending: ${self.pendingExecutions}`
|
|
863
|
+
);
|
|
864
|
+
if (self.pendingExecutions === 0 && executionCompletionResolver) {
|
|
865
|
+
self.logger?.log(
|
|
866
|
+
`[AiAgentKit][lifecycle] All executions finished. Resolving promise.`
|
|
867
|
+
);
|
|
868
|
+
executionCompletionResolver();
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
ctx.next = new NextHandler(
|
|
873
|
+
ctx,
|
|
874
|
+
self,
|
|
875
|
+
ctx._onExecutionStart,
|
|
876
|
+
ctx._onExecutionEnd
|
|
877
|
+
);
|
|
878
|
+
ctx._onExecutionStart();
|
|
879
|
+
self.logger?.log(
|
|
880
|
+
`[AiAgentKit][lifecycle] Main execution chain started.`
|
|
881
|
+
);
|
|
882
|
+
try {
|
|
883
|
+
const response = await self._execute(path2, ctx);
|
|
884
|
+
const toolDefinition = this.actAsToolDefinitions.get(path2);
|
|
885
|
+
if (toolDefinition && !toolDefinition.metadata?.hideUI) {
|
|
886
|
+
ctx.response.writeCustomTool({
|
|
887
|
+
toolName: toolDefinition.id,
|
|
888
|
+
toolCallId: toolDefinition.id + "-" + ctx.response.generateId(),
|
|
889
|
+
output: response
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
return response;
|
|
893
|
+
} catch (err) {
|
|
894
|
+
ctx.logger.error("Unhandled error in main execution chain", err);
|
|
895
|
+
} finally {
|
|
896
|
+
ctx._onExecutionEnd();
|
|
897
|
+
self.logger?.log(
|
|
898
|
+
`[AiAgentKit][lifecycle] Main execution chain finished.`
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
await executionCompletionPromise;
|
|
902
|
+
self.logger?.log(
|
|
903
|
+
`[AiAgentKit][lifecycle] All executions truly finished. Stream can be safely closed.`
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Handles an incoming request and returns a promise that resolves with the full,
|
|
910
|
+
* non-streamed response. This is useful for environments where streaming is not
|
|
911
|
+
* desired or for testing.
|
|
912
|
+
*
|
|
913
|
+
* @param path The path of the agent or tool to execute.
|
|
914
|
+
* @param initialContext The initial context for the request, typically containing messages.
|
|
915
|
+
* @returns A `Promise<Response>` that resolves with the final JSON response.
|
|
916
|
+
*/
|
|
917
|
+
async toAwaitResponse(path2, initialContext) {
|
|
918
|
+
this.logger?.log(`Handling request for path: ${path2}`);
|
|
919
|
+
const self = this;
|
|
920
|
+
let executionCompletionResolver = null;
|
|
921
|
+
const executionCompletionPromise = new Promise((resolve) => {
|
|
922
|
+
executionCompletionResolver = resolve;
|
|
923
|
+
});
|
|
924
|
+
const stream = this.handleStream(
|
|
925
|
+
path2,
|
|
926
|
+
initialContext,
|
|
927
|
+
executionCompletionPromise,
|
|
928
|
+
executionCompletionResolver
|
|
929
|
+
);
|
|
930
|
+
const messageStream = (0, import_ai.readUIMessageStream)({
|
|
931
|
+
stream,
|
|
932
|
+
onError: (error) => {
|
|
933
|
+
this.logger?.error("Error reading UI message stream", error);
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
let finalMessages = [];
|
|
937
|
+
const thisMessageId = (0, import_ai.generateId)();
|
|
938
|
+
for await (const message of messageStream) {
|
|
939
|
+
if (message.id?.length > 0) {
|
|
940
|
+
finalMessages.push(message);
|
|
941
|
+
} else if (finalMessages.find((m) => m.id === thisMessageId)) {
|
|
942
|
+
finalMessages = finalMessages.map(
|
|
943
|
+
(m) => m.id === thisMessageId ? {
|
|
944
|
+
...m,
|
|
945
|
+
metadata: {
|
|
946
|
+
...m.metadata ?? {},
|
|
947
|
+
...message.metadata ?? {}
|
|
948
|
+
},
|
|
949
|
+
parts: clubParts([
|
|
950
|
+
...m.parts ?? [],
|
|
951
|
+
...message.parts ?? []
|
|
952
|
+
])
|
|
953
|
+
} : m
|
|
954
|
+
);
|
|
955
|
+
} else {
|
|
956
|
+
finalMessages.push({
|
|
957
|
+
...message,
|
|
958
|
+
id: thisMessageId
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
const responseBody = JSON.stringify(finalMessages);
|
|
963
|
+
return new Response(responseBody, {
|
|
964
|
+
headers: { "Content-Type": "application/json" }
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
var NextHandler = class {
|
|
969
|
+
constructor(ctx, router, onExecutionStart, onExecutionEnd, parentNext) {
|
|
970
|
+
this.ctx = ctx;
|
|
971
|
+
this.router = router;
|
|
972
|
+
this.onExecutionStart = onExecutionStart;
|
|
973
|
+
this.onExecutionEnd = onExecutionEnd;
|
|
974
|
+
this.maxCallDepth = this.router.options.maxCallDepth;
|
|
975
|
+
}
|
|
976
|
+
async callAgent(agentPath, params, options) {
|
|
977
|
+
this.onExecutionStart();
|
|
978
|
+
try {
|
|
979
|
+
const currentDepth = this.ctx.executionContext.callDepth ?? 0;
|
|
980
|
+
if (currentDepth >= this.maxCallDepth) {
|
|
981
|
+
const err = new MaxCallDepthExceededError(this.maxCallDepth);
|
|
982
|
+
this.ctx.logger.error(`[callAgent] Aborting. ${err.message}`);
|
|
983
|
+
throw err;
|
|
984
|
+
}
|
|
985
|
+
const parentPath = this.ctx.executionContext.currentPath || "/";
|
|
986
|
+
const resolvedPath = this.router._resolvePath(
|
|
987
|
+
parentPath,
|
|
988
|
+
agentPath
|
|
989
|
+
);
|
|
990
|
+
this.ctx.logger.log(`Calling agent: resolvedPath='${resolvedPath}'`);
|
|
991
|
+
const subContext = this.router._createSubContext(this.ctx, {
|
|
992
|
+
type: "agent",
|
|
993
|
+
path: resolvedPath,
|
|
994
|
+
params: params ?? {},
|
|
995
|
+
messages: this.ctx.request.messages
|
|
996
|
+
});
|
|
997
|
+
const definition = this.router.actAsToolDefinitions.get(resolvedPath);
|
|
998
|
+
const toolCallId = definition?.id + "-" + this.ctx.response.generateId();
|
|
999
|
+
if (options?.streamToUI && definition) {
|
|
1000
|
+
this.ctx.response.writeCustomTool({
|
|
1001
|
+
toolName: definition?.id,
|
|
1002
|
+
toolCallId,
|
|
1003
|
+
input: subContext.request.params
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
const data = await this.router._execute(
|
|
1007
|
+
resolvedPath,
|
|
1008
|
+
subContext,
|
|
1009
|
+
true
|
|
1010
|
+
);
|
|
1011
|
+
if (options?.streamToUI && definition) {
|
|
1012
|
+
this.ctx.response.writeCustomTool({
|
|
1013
|
+
toolName: definition?.id,
|
|
1014
|
+
toolCallId,
|
|
1015
|
+
output: data
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
return { ok: true, data };
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
this.ctx.logger.error(`[callAgent] Error:`, error);
|
|
1021
|
+
return { ok: false, error };
|
|
1022
|
+
} finally {
|
|
1023
|
+
this.onExecutionEnd();
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
agentAsTool(agentPath, schemaControl) {
|
|
1027
|
+
const parentPath = this.ctx.executionContext.currentPath || "/";
|
|
1028
|
+
const resolvedPath = this.router._resolvePath(
|
|
1029
|
+
parentPath,
|
|
1030
|
+
agentPath
|
|
1031
|
+
);
|
|
1032
|
+
const definition = this.getToolDefinition(agentPath);
|
|
1033
|
+
if (!definition) {
|
|
1034
|
+
this.ctx.logger.error(
|
|
1035
|
+
`[agentAsTool] No definition found for agent at resolved path: ${resolvedPath}`
|
|
1036
|
+
);
|
|
1037
|
+
throw new AgentDefinitionMissingError(resolvedPath);
|
|
1038
|
+
}
|
|
1039
|
+
const originalSchema = definition.inputSchema;
|
|
1040
|
+
let finalSchema = originalSchema;
|
|
1041
|
+
const fixedParams = {};
|
|
1042
|
+
if (schemaControl) {
|
|
1043
|
+
if (schemaControl.disableAllInputs) {
|
|
1044
|
+
finalSchema = import_zod.z.object({});
|
|
1045
|
+
} else if (originalSchema) {
|
|
1046
|
+
let isPickMode = false;
|
|
1047
|
+
for (const key in schemaControl) {
|
|
1048
|
+
if (schemaControl[key] === true) {
|
|
1049
|
+
isPickMode = true;
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (isPickMode) {
|
|
1054
|
+
const pickShape = {};
|
|
1055
|
+
for (const key in schemaControl) {
|
|
1056
|
+
if (schemaControl[key] === true) {
|
|
1057
|
+
pickShape[key] = true;
|
|
1058
|
+
} else {
|
|
1059
|
+
fixedParams[key] = schemaControl[key];
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
finalSchema = originalSchema.pick(pickShape);
|
|
1063
|
+
} else {
|
|
1064
|
+
const omitShape = {};
|
|
1065
|
+
for (const key in schemaControl) {
|
|
1066
|
+
if (key !== "disableAllInputs") {
|
|
1067
|
+
fixedParams[key] = schemaControl[key];
|
|
1068
|
+
omitShape[key] = true;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
finalSchema = originalSchema.omit(omitShape);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const { id, metadata, ...restDefinition } = definition;
|
|
1076
|
+
restDefinition.inputSchema = finalSchema;
|
|
1077
|
+
return {
|
|
1078
|
+
[id]: {
|
|
1079
|
+
...restDefinition,
|
|
1080
|
+
metadata: {
|
|
1081
|
+
...metadata,
|
|
1082
|
+
toolKey: id,
|
|
1083
|
+
name: restDefinition.name,
|
|
1084
|
+
description: restDefinition.description,
|
|
1085
|
+
absolutePath: resolvedPath
|
|
1086
|
+
},
|
|
1087
|
+
execute: async (params, options) => {
|
|
1088
|
+
const result = await this.callAgent(agentPath, params, options);
|
|
1089
|
+
if (!result.ok) {
|
|
1090
|
+
throw result.error;
|
|
1091
|
+
}
|
|
1092
|
+
return result.data;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
getToolDefinition(agentPath) {
|
|
1098
|
+
const parentPath = this.ctx.executionContext.currentPath || "/";
|
|
1099
|
+
const resolvedPath = this.router._resolvePath(
|
|
1100
|
+
parentPath,
|
|
1101
|
+
agentPath.toString()
|
|
1102
|
+
);
|
|
1103
|
+
let preDefined;
|
|
1104
|
+
const pathsToTry = [resolvedPath];
|
|
1105
|
+
if (typeof agentPath === "string" && agentPath.startsWith("/")) {
|
|
1106
|
+
pathsToTry.unshift(agentPath);
|
|
1107
|
+
}
|
|
1108
|
+
for (const pathToTry of pathsToTry) {
|
|
1109
|
+
for (const [key, value] of this.router.actAsToolDefinitions) {
|
|
1110
|
+
if (typeof key === "string") {
|
|
1111
|
+
if (key === pathToTry) {
|
|
1112
|
+
preDefined = value;
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
if (extractPathParams(key, pathToTry) !== null) {
|
|
1116
|
+
preDefined = value;
|
|
1117
|
+
break;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (key instanceof RegExp && key.test(pathToTry)) {
|
|
1121
|
+
preDefined = value;
|
|
1122
|
+
break;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
if (preDefined) break;
|
|
1126
|
+
}
|
|
1127
|
+
const definition = preDefined;
|
|
1128
|
+
if (!definition) {
|
|
1129
|
+
this.ctx.logger.error(
|
|
1130
|
+
`[agentAsTool] No definition found for agent at resolved path: ${resolvedPath}`
|
|
1131
|
+
);
|
|
1132
|
+
return void 0;
|
|
1133
|
+
}
|
|
1134
|
+
const { metadata, ...restDefinition } = definition;
|
|
1135
|
+
return {
|
|
1136
|
+
...restDefinition,
|
|
1137
|
+
metadata: {
|
|
1138
|
+
...metadata,
|
|
1139
|
+
toolKey: restDefinition.id,
|
|
1140
|
+
name: restDefinition.name,
|
|
1141
|
+
description: restDefinition.description,
|
|
1142
|
+
absolutePath: resolvedPath
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1148
|
+
0 && (module.exports = {
|
|
1149
|
+
AgentDefinitionMissingError,
|
|
1150
|
+
AgentNotFoundError,
|
|
1151
|
+
AiKitError,
|
|
1152
|
+
AiRouter,
|
|
1153
|
+
MaxCallDepthExceededError,
|
|
1154
|
+
MemoryStore,
|
|
1155
|
+
StreamWriter,
|
|
1156
|
+
findFirstElement,
|
|
1157
|
+
findLastElement,
|
|
1158
|
+
findLastMessageWith,
|
|
1159
|
+
getGlobalLogger,
|
|
1160
|
+
getTextParts,
|
|
1161
|
+
getTextPartsContent,
|
|
1162
|
+
setGlobalLogger
|
|
1163
|
+
});
|
|
1164
|
+
//# sourceMappingURL=index.js.map
|