@cognipeer/agent-sdk 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -103
- package/dist/index.cjs +2203 -635
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +545 -115
- package/dist/index.d.ts +545 -115
- package/dist/index.mjs +2797 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -26
- package/dist/index.js +0 -1212
- package/dist/index.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,60 +1,136 @@
|
|
|
1
|
-
|
|
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);
|
|
1
|
+
'use strict';
|
|
29
2
|
|
|
30
|
-
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
var module$1 = require('module');
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var buffer = require('buffer');
|
|
8
|
+
var nanoid = require('nanoid');
|
|
9
|
+
var AjvModule = require('ajv');
|
|
10
|
+
|
|
11
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
12
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
|
|
14
|
+
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
15
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
16
|
+
var AjvModule__default = /*#__PURE__*/_interopDefault(AjvModule);
|
|
17
|
+
|
|
18
|
+
// src/adapters/langchain.ts
|
|
19
|
+
var moduleName = "@langchain/core/tools";
|
|
20
|
+
var cachedToolFactory;
|
|
21
|
+
var cachedHasAttemptedFactory = false;
|
|
22
|
+
var requireFn = null;
|
|
23
|
+
try {
|
|
24
|
+
requireFn = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
25
|
+
} catch {
|
|
26
|
+
requireFn = null;
|
|
27
|
+
}
|
|
28
|
+
function getExecutor(tool) {
|
|
29
|
+
if (typeof tool.invoke === "function") return (input) => tool.invoke(input);
|
|
30
|
+
if (typeof tool.call === "function") return (input) => tool.call(input);
|
|
31
|
+
if (typeof tool.func === "function") return (input) => tool.func(input);
|
|
32
|
+
if (typeof tool.run === "function") return (input) => tool.run(input);
|
|
33
|
+
if (typeof tool === "function") {
|
|
34
|
+
return tool;
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Tool ${tool?.name ?? "<unnamed>"} is not invokable`);
|
|
37
|
+
}
|
|
38
|
+
function loadLangchainToolFactory() {
|
|
39
|
+
if (cachedHasAttemptedFactory) return cachedToolFactory ?? null;
|
|
40
|
+
cachedHasAttemptedFactory = true;
|
|
41
|
+
cachedToolFactory = null;
|
|
42
|
+
if (!requireFn) return null;
|
|
43
|
+
try {
|
|
44
|
+
const mod = requireFn(moduleName);
|
|
45
|
+
const factory = typeof mod?.tool === "function" ? mod.tool : void 0;
|
|
46
|
+
if (typeof factory === "function") {
|
|
47
|
+
cachedToolFactory = factory;
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
cachedToolFactory = null;
|
|
51
|
+
}
|
|
52
|
+
return cachedToolFactory ?? null;
|
|
53
|
+
}
|
|
54
|
+
function ensureSchema(candidate) {
|
|
55
|
+
if (!candidate) return zod.z.any();
|
|
56
|
+
if (candidate instanceof zod.z.ZodType) return candidate;
|
|
57
|
+
return candidate;
|
|
58
|
+
}
|
|
59
|
+
function toLangchainTools(tools) {
|
|
60
|
+
const factory = loadLangchainToolFactory();
|
|
61
|
+
if (!factory) return tools;
|
|
62
|
+
return tools.map((tool) => {
|
|
63
|
+
if (!tool) return tool;
|
|
64
|
+
if (tool.__lcTool) return tool.__lcTool;
|
|
65
|
+
const executor = getExecutor(tool);
|
|
66
|
+
try {
|
|
67
|
+
const lcTool = factory(async (input) => executor(input), {
|
|
68
|
+
name: tool.name,
|
|
69
|
+
description: tool.description,
|
|
70
|
+
schema: ensureSchema(tool.schema)
|
|
71
|
+
});
|
|
72
|
+
tool.__lcTool = lcTool;
|
|
73
|
+
return lcTool;
|
|
74
|
+
} catch {
|
|
75
|
+
return tool;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function fromLangchainTools(tools) {
|
|
80
|
+
return tools.map((tool) => {
|
|
81
|
+
if (!tool) return tool;
|
|
82
|
+
if (tool.__source === "smart") return tool;
|
|
83
|
+
if (typeof tool === "function" && !tool.name) {
|
|
84
|
+
const wrapped2 = createSimpleTool({
|
|
85
|
+
name: "anonymous_tool",
|
|
86
|
+
description: void 0,
|
|
87
|
+
schema: zod.z.any(),
|
|
88
|
+
executor: tool
|
|
89
|
+
});
|
|
90
|
+
wrapped2.__source = "langchain";
|
|
91
|
+
wrapped2.__lc = tool;
|
|
92
|
+
return wrapped2;
|
|
93
|
+
}
|
|
94
|
+
const name = tool.name || tool?.lc_kwargs?.name || tool?.config?.name || "tool";
|
|
95
|
+
const description = tool.description || tool?.lc_kwargs?.description;
|
|
96
|
+
const schema = tool.schema || tool?.lc_kwargs?.schema || zod.z.any();
|
|
97
|
+
const executor = (input) => {
|
|
98
|
+
if (typeof tool.invoke === "function") return tool.invoke(input);
|
|
99
|
+
if (typeof tool.call === "function") return tool.call(input);
|
|
100
|
+
if (typeof tool._call === "function") return tool._call(input);
|
|
101
|
+
if (typeof tool.run === "function") return tool.run(input);
|
|
102
|
+
if (typeof tool.func === "function") return tool.func(input);
|
|
103
|
+
if (typeof tool === "function") return tool(input);
|
|
104
|
+
throw new Error(`LangChain tool ${name} is not invokable`);
|
|
105
|
+
};
|
|
106
|
+
const wrapped = createSimpleTool({
|
|
107
|
+
name,
|
|
108
|
+
description,
|
|
109
|
+
schema,
|
|
110
|
+
executor
|
|
111
|
+
});
|
|
112
|
+
wrapped.__source = "langchain";
|
|
113
|
+
wrapped.__lc = tool;
|
|
114
|
+
return wrapped;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function createSimpleTool({
|
|
118
|
+
name,
|
|
119
|
+
description,
|
|
120
|
+
schema,
|
|
121
|
+
executor
|
|
122
|
+
}) {
|
|
123
|
+
const invoke = async (input) => executor(input);
|
|
124
|
+
return {
|
|
125
|
+
name,
|
|
126
|
+
description,
|
|
127
|
+
schema,
|
|
128
|
+
invoke,
|
|
129
|
+
call: invoke,
|
|
130
|
+
run: invoke,
|
|
131
|
+
func: invoke
|
|
132
|
+
};
|
|
133
|
+
}
|
|
58
134
|
|
|
59
135
|
// src/model.ts
|
|
60
136
|
function isSmartModel(m) {
|
|
@@ -115,8 +191,13 @@ function fromLangchainModel(lcModel) {
|
|
|
115
191
|
return { role: "assistant", content: String(response ?? "") };
|
|
116
192
|
},
|
|
117
193
|
bindTools: (tools) => {
|
|
194
|
+
const lcReady = toLangchainTools(tools);
|
|
118
195
|
if (typeof lcModel.bindTools === "function") {
|
|
119
|
-
const bound = lcModel.bindTools(
|
|
196
|
+
const bound = lcModel.bindTools(lcReady);
|
|
197
|
+
return fromLangchainModel(bound);
|
|
198
|
+
}
|
|
199
|
+
if (typeof lcModel.bind === "function") {
|
|
200
|
+
const bound = lcModel.bind({ tools: lcReady });
|
|
120
201
|
return fromLangchainModel(bound);
|
|
121
202
|
}
|
|
122
203
|
return adapted;
|
|
@@ -128,28 +209,38 @@ function fromLangchainModel(lcModel) {
|
|
|
128
209
|
}
|
|
129
210
|
|
|
130
211
|
// src/tool.ts
|
|
131
|
-
|
|
132
|
-
function createSmartTool({
|
|
212
|
+
function createTool({
|
|
133
213
|
name,
|
|
134
214
|
description,
|
|
135
215
|
schema,
|
|
136
|
-
func
|
|
216
|
+
func,
|
|
217
|
+
needsApproval,
|
|
218
|
+
approvalPrompt,
|
|
219
|
+
approvalDefaults
|
|
137
220
|
}) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}, {
|
|
221
|
+
const execute = async (input) => func(input);
|
|
222
|
+
const toolRecord = {
|
|
141
223
|
name,
|
|
142
224
|
description,
|
|
143
|
-
schema
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
225
|
+
schema,
|
|
226
|
+
// Our runtime prefers invoke but fallback helpers keep compatibility with LC-style tools.
|
|
227
|
+
invoke: execute,
|
|
228
|
+
call: execute,
|
|
229
|
+
run: execute,
|
|
230
|
+
func: execute
|
|
231
|
+
};
|
|
232
|
+
if (typeof needsApproval === "boolean") {
|
|
233
|
+
toolRecord.needsApproval = needsApproval;
|
|
234
|
+
}
|
|
235
|
+
if (approvalPrompt !== void 0) {
|
|
236
|
+
toolRecord.approvalPrompt = approvalPrompt;
|
|
237
|
+
}
|
|
238
|
+
if (approvalDefaults !== void 0) {
|
|
239
|
+
toolRecord.approvalDefaults = approvalDefaults;
|
|
240
|
+
}
|
|
241
|
+
toolRecord.__source = toolRecord.__source || "smart";
|
|
242
|
+
toolRecord.__impl = func;
|
|
243
|
+
return toolRecord;
|
|
153
244
|
}
|
|
154
245
|
|
|
155
246
|
// src/prompts.ts
|
|
@@ -181,8 +272,12 @@ Rules:
|
|
|
181
272
|
].filter(Boolean).join("\n");
|
|
182
273
|
}
|
|
183
274
|
|
|
184
|
-
// src/
|
|
185
|
-
var
|
|
275
|
+
// src/types.ts
|
|
276
|
+
var GuardrailPhase = /* @__PURE__ */ ((GuardrailPhase2) => {
|
|
277
|
+
GuardrailPhase2["Request"] = "request";
|
|
278
|
+
GuardrailPhase2["Response"] = "response";
|
|
279
|
+
return GuardrailPhase2;
|
|
280
|
+
})(GuardrailPhase || {});
|
|
186
281
|
|
|
187
282
|
// src/nodes/resolver.ts
|
|
188
283
|
function createResolverNode() {
|
|
@@ -196,6 +291,7 @@ function createResolverNode() {
|
|
|
196
291
|
toolCache: state.toolCache || {},
|
|
197
292
|
toolHistory: state.toolHistory || [],
|
|
198
293
|
toolHistoryArchived: state.toolHistoryArchived || [],
|
|
294
|
+
pendingApprovals: state.pendingApprovals || [],
|
|
199
295
|
plan: state.plan || null,
|
|
200
296
|
planVersion: state.planVersion || 0
|
|
201
297
|
};
|
|
@@ -247,157 +343,744 @@ function safe(v) {
|
|
|
247
343
|
const n = Number(v);
|
|
248
344
|
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
249
345
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
function
|
|
255
|
-
if (
|
|
346
|
+
var DEFAULT_COGNIPEER_URL = "https://api.cognipeer.com/v1/client/tracing/sessions";
|
|
347
|
+
function fileSink(path2) {
|
|
348
|
+
return { type: "file", path: path2 };
|
|
349
|
+
}
|
|
350
|
+
function customSink(handler) {
|
|
351
|
+
if (typeof handler === "function") {
|
|
352
|
+
return { type: "custom", onEvent: handler };
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
type: "custom",
|
|
356
|
+
onEvent: typeof handler?.onEvent === "function" ? handler.onEvent : void 0,
|
|
357
|
+
onSession: typeof handler?.onSession === "function" ? handler.onSession : void 0
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function cognipeerSink(first, second) {
|
|
361
|
+
if (second === void 0) {
|
|
362
|
+
const apiKey = first ?? "";
|
|
363
|
+
return { type: "cognipeer", apiKey };
|
|
364
|
+
}
|
|
365
|
+
const url = typeof first === "string" && first.trim().length > 0 ? first.trim() : void 0;
|
|
366
|
+
return { type: "cognipeer", url, apiKey: second };
|
|
367
|
+
}
|
|
368
|
+
function httpSink(url, headers) {
|
|
369
|
+
return { type: "http", url, headers };
|
|
256
370
|
}
|
|
257
|
-
function resolveLogsBaseDir(customPath) {
|
|
371
|
+
function resolveLogsBaseDir(customPath, ensureDirectory = true) {
|
|
258
372
|
const root = process.cwd();
|
|
259
|
-
const base = customPath && customPath.trim().length > 0 ? customPath :
|
|
260
|
-
|
|
373
|
+
const base = customPath && customPath.trim().length > 0 ? customPath : path__default.default.join(root, "logs");
|
|
374
|
+
if (ensureDirectory) {
|
|
375
|
+
ensureDir(base);
|
|
376
|
+
}
|
|
261
377
|
return base;
|
|
262
378
|
}
|
|
263
|
-
function
|
|
264
|
-
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
|
|
379
|
+
function coerceToString(value) {
|
|
380
|
+
if (value == null) return void 0;
|
|
381
|
+
if (typeof value === "string") {
|
|
382
|
+
const trimmed = value.trim();
|
|
383
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
384
|
+
}
|
|
385
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
386
|
+
return String(value);
|
|
387
|
+
}
|
|
388
|
+
if (typeof value === "function" && value.length === 0) {
|
|
389
|
+
try {
|
|
390
|
+
return coerceToString(value());
|
|
391
|
+
} catch {
|
|
392
|
+
return void 0;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (Array.isArray(value)) {
|
|
396
|
+
for (const item of value) {
|
|
397
|
+
const normalized = coerceToString(item);
|
|
398
|
+
if (normalized) return normalized;
|
|
399
|
+
}
|
|
400
|
+
return void 0;
|
|
401
|
+
}
|
|
402
|
+
return void 0;
|
|
271
403
|
}
|
|
272
|
-
function
|
|
273
|
-
|
|
274
|
-
|
|
404
|
+
function firstNonEmptyString(...candidates) {
|
|
405
|
+
for (const candidate of candidates) {
|
|
406
|
+
const normalized = coerceToString(candidate);
|
|
407
|
+
if (normalized) return normalized;
|
|
408
|
+
}
|
|
409
|
+
return void 0;
|
|
275
410
|
}
|
|
276
411
|
function getModelName(model) {
|
|
277
|
-
|
|
412
|
+
if (!model) return void 0;
|
|
413
|
+
const maybeLc = model?._lc;
|
|
414
|
+
const direct = firstNonEmptyString(
|
|
415
|
+
model?.model,
|
|
416
|
+
model?.options?.model,
|
|
417
|
+
model?.params?.model,
|
|
418
|
+
model?.config?.model,
|
|
419
|
+
model?.configuration?.model,
|
|
420
|
+
model?.metadata?.model,
|
|
421
|
+
model?.lc_kwargs?.model,
|
|
422
|
+
maybeLc?.model,
|
|
423
|
+
maybeLc?.options?.model,
|
|
424
|
+
maybeLc?.params?.model,
|
|
425
|
+
maybeLc?.config?.model,
|
|
426
|
+
maybeLc?.configuration?.model,
|
|
427
|
+
maybeLc?.metadata?.model,
|
|
428
|
+
maybeLc?.lc_kwargs?.model,
|
|
429
|
+
model?.metadata?.modelName,
|
|
430
|
+
maybeLc?.metadata?.modelName,
|
|
431
|
+
model?.modelName,
|
|
432
|
+
maybeLc?.modelName,
|
|
433
|
+
model?.lc_alias,
|
|
434
|
+
model?.model_id,
|
|
435
|
+
model?.id,
|
|
436
|
+
maybeLc?.model_id,
|
|
437
|
+
maybeLc?.id,
|
|
438
|
+
model?._model,
|
|
439
|
+
model?._modelName,
|
|
440
|
+
model?.__model
|
|
441
|
+
);
|
|
442
|
+
if (direct) return direct;
|
|
443
|
+
const maybeClient = model?.client || model?.api || model?.service;
|
|
444
|
+
const clientValue = firstNonEmptyString(
|
|
445
|
+
maybeClient?.model,
|
|
446
|
+
maybeClient?.config?.model,
|
|
447
|
+
maybeClient?.options?.model,
|
|
448
|
+
maybeClient?.default?.model
|
|
449
|
+
);
|
|
450
|
+
if (clientValue) return clientValue;
|
|
451
|
+
const fnNames = firstNonEmptyString(model?._modelId, model?._llmType, maybeLc?._modelId, maybeLc?._llmType, model?.constructor?.name);
|
|
452
|
+
return fnNames;
|
|
278
453
|
}
|
|
279
|
-
function
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
454
|
+
function getProviderName(model) {
|
|
455
|
+
if (!model) return void 0;
|
|
456
|
+
const direct = firstNonEmptyString(
|
|
457
|
+
model?.provider,
|
|
458
|
+
model?.options?.provider,
|
|
459
|
+
model?.params?.provider,
|
|
460
|
+
model?.config?.provider,
|
|
461
|
+
model?.configuration?.provider,
|
|
462
|
+
model?.metadata?.provider,
|
|
463
|
+
model?.lc_kwargs?.provider,
|
|
464
|
+
model?.lc_alias,
|
|
465
|
+
model?.__provider
|
|
466
|
+
);
|
|
467
|
+
if (direct) return direct;
|
|
468
|
+
const maybeLc = model?._lc;
|
|
469
|
+
const lcValue = maybeLc ? firstNonEmptyString(
|
|
470
|
+
maybeLc?.provider,
|
|
471
|
+
maybeLc?.options?.provider,
|
|
472
|
+
maybeLc?.config?.provider,
|
|
473
|
+
maybeLc?.configuration?.provider,
|
|
474
|
+
maybeLc?.metadata?.provider,
|
|
475
|
+
maybeLc?.lc_kwargs?.provider,
|
|
476
|
+
maybeLc?.client?.config?.provider
|
|
477
|
+
) : void 0;
|
|
478
|
+
if (lcValue) return lcValue;
|
|
479
|
+
const maybeClient = model?.client || model?.api || model?.service;
|
|
480
|
+
return firstNonEmptyString(
|
|
481
|
+
maybeClient?.provider,
|
|
482
|
+
maybeClient?.config?.provider,
|
|
483
|
+
maybeClient?.options?.provider,
|
|
484
|
+
maybeClient?.metadata?.provider
|
|
485
|
+
);
|
|
293
486
|
}
|
|
294
|
-
function
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
487
|
+
function inferModelFromMessages(messageList) {
|
|
488
|
+
if (!Array.isArray(messageList)) return void 0;
|
|
489
|
+
for (let i = messageList.length - 1; i >= 0; i -= 1) {
|
|
490
|
+
const message = messageList[i];
|
|
491
|
+
if (!message) continue;
|
|
492
|
+
const candidate = firstNonEmptyString(
|
|
493
|
+
message?.metadata?.model,
|
|
494
|
+
message?.metadata?.modelName,
|
|
495
|
+
message?.metadata?.modelNames,
|
|
496
|
+
message?.metadata?.model_id,
|
|
497
|
+
message?.response_metadata?.model,
|
|
498
|
+
message?.response_metadata?.modelName,
|
|
499
|
+
message?.response_metadata?.modelNames,
|
|
500
|
+
message?.response_metadata?.model_id,
|
|
501
|
+
message?.usage_metadata?.model,
|
|
502
|
+
message?.model,
|
|
503
|
+
message?.modelName,
|
|
504
|
+
message?.model_id,
|
|
505
|
+
message?.additional_kwargs?.model,
|
|
506
|
+
message?.additional_kwargs?.modelName,
|
|
507
|
+
message?.info?.model,
|
|
508
|
+
message?.annotations?.model
|
|
307
509
|
);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
function
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
510
|
+
if (candidate) return candidate;
|
|
511
|
+
}
|
|
512
|
+
return void 0;
|
|
513
|
+
}
|
|
514
|
+
function inferProviderFromMessages(messageList) {
|
|
515
|
+
if (!Array.isArray(messageList)) return void 0;
|
|
516
|
+
for (let i = messageList.length - 1; i >= 0; i -= 1) {
|
|
517
|
+
const message = messageList[i];
|
|
518
|
+
if (!message) continue;
|
|
519
|
+
const candidate = firstNonEmptyString(
|
|
520
|
+
message?.metadata?.provider,
|
|
521
|
+
message?.metadata?.providers,
|
|
522
|
+
message?.response_metadata?.provider,
|
|
523
|
+
message?.response_metadata?.providers,
|
|
524
|
+
message?.usage_metadata?.provider,
|
|
525
|
+
message?.provider,
|
|
526
|
+
message?.additional_kwargs?.provider,
|
|
527
|
+
message?.info?.provider,
|
|
528
|
+
message?.annotations?.provider
|
|
529
|
+
);
|
|
530
|
+
if (candidate) return candidate;
|
|
531
|
+
}
|
|
532
|
+
return void 0;
|
|
533
|
+
}
|
|
534
|
+
var DEFAULT_TRACE_CONFIG = {
|
|
535
|
+
logData: true
|
|
536
|
+
};
|
|
537
|
+
function ensureDir(p) {
|
|
538
|
+
if (!fs__default.default.existsSync(p)) {
|
|
539
|
+
fs__default.default.mkdirSync(p, { recursive: true });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function withDefaults(config) {
|
|
543
|
+
const enabled = Boolean(config?.enabled);
|
|
544
|
+
const logData = config?.logData ?? DEFAULT_TRACE_CONFIG.logData;
|
|
545
|
+
let sink;
|
|
546
|
+
try {
|
|
547
|
+
sink = resolveSink(config?.sink);
|
|
548
|
+
} catch (err) {
|
|
549
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
550
|
+
console.warn("Invalid tracing sink configuration. Falling back to file sink.", err);
|
|
551
|
+
}
|
|
552
|
+
sink = resolveSink({ type: "file" });
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
enabled,
|
|
556
|
+
logData,
|
|
557
|
+
mode: "batched",
|
|
558
|
+
sink
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function resolveSink(sink) {
|
|
562
|
+
const candidate = sink ?? { type: "file" };
|
|
563
|
+
switch (candidate.type) {
|
|
564
|
+
case "file": {
|
|
565
|
+
const baseDir = resolveLogsBaseDir(candidate.path, false);
|
|
566
|
+
return { type: "file", baseDir };
|
|
567
|
+
}
|
|
568
|
+
case "custom": {
|
|
569
|
+
const onEvent = typeof candidate.onEvent === "function" ? candidate.onEvent : void 0;
|
|
570
|
+
const onSession = typeof candidate.onSession === "function" ? candidate.onSession : void 0;
|
|
571
|
+
return { type: "custom", onEvent, onSession };
|
|
572
|
+
}
|
|
573
|
+
case "cognipeer": {
|
|
574
|
+
const apiKey = typeof candidate.apiKey === "string" ? candidate.apiKey.trim() : "";
|
|
575
|
+
if (!apiKey) {
|
|
576
|
+
throw new Error("cognipeer sink requires a non-empty apiKey");
|
|
577
|
+
}
|
|
578
|
+
const url = typeof candidate.url === "string" && candidate.url.trim().length > 0 ? candidate.url.trim() : DEFAULT_COGNIPEER_URL;
|
|
579
|
+
return { type: "cognipeer", url, apiKey };
|
|
580
|
+
}
|
|
581
|
+
case "http": {
|
|
582
|
+
const url = typeof candidate.url === "string" ? candidate.url.trim() : "";
|
|
583
|
+
if (!url) {
|
|
584
|
+
throw new Error("http sink requires a non-empty url");
|
|
336
585
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
586
|
+
const headers = candidate.headers ? { ...candidate.headers } : void 0;
|
|
587
|
+
return headers ? { type: "http", url, headers } : { type: "http", url };
|
|
588
|
+
}
|
|
589
|
+
default: {
|
|
590
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
591
|
+
console.warn("Unknown tracing sink type. Falling back to file sink.", candidate);
|
|
592
|
+
}
|
|
593
|
+
return resolveSink({ type: "file" });
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function snapshotSink(runtime) {
|
|
598
|
+
const sink = runtime.resolvedConfig.sink;
|
|
599
|
+
switch (sink.type) {
|
|
600
|
+
case "file":
|
|
601
|
+
return { type: "file", path: runtime.fileBaseDir || sink.baseDir };
|
|
602
|
+
case "custom":
|
|
603
|
+
return { type: "custom" };
|
|
604
|
+
case "cognipeer":
|
|
605
|
+
return { type: "cognipeer", url: sink.url };
|
|
606
|
+
case "http":
|
|
607
|
+
return { type: "http", url: sink.url };
|
|
608
|
+
default:
|
|
609
|
+
return { type: "custom" };
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function buildConfigSnapshot(runtime) {
|
|
613
|
+
return {
|
|
614
|
+
enabled: runtime.resolvedConfig.enabled,
|
|
615
|
+
logData: runtime.resolvedConfig.logData,
|
|
616
|
+
sink: snapshotSink(runtime)
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
async function postTraceSession(url, headers, payload) {
|
|
620
|
+
const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
|
|
621
|
+
if (!fetchFn) {
|
|
622
|
+
throw new Error("HTTP sink requires fetch to be available in this runtime.");
|
|
623
|
+
}
|
|
624
|
+
const finalHeaders = { ...headers || {} };
|
|
625
|
+
if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
|
|
626
|
+
finalHeaders["content-type"] = "application/json";
|
|
627
|
+
}
|
|
628
|
+
const response = await fetchFn(url, {
|
|
629
|
+
method: "POST",
|
|
630
|
+
headers: finalHeaders,
|
|
631
|
+
body: JSON.stringify(payload)
|
|
632
|
+
});
|
|
633
|
+
if (!response.ok) {
|
|
634
|
+
let responseText = "";
|
|
635
|
+
try {
|
|
636
|
+
responseText = await response.text();
|
|
637
|
+
} catch {
|
|
638
|
+
}
|
|
639
|
+
const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
|
|
640
|
+
const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
|
|
641
|
+
throw new Error(`${statusLine}${bodyPreview}`);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function createEmptySummary() {
|
|
645
|
+
return {
|
|
646
|
+
totalDurationMs: 0,
|
|
647
|
+
totalInputTokens: 0,
|
|
648
|
+
totalOutputTokens: 0,
|
|
649
|
+
totalCachedInputTokens: 0,
|
|
650
|
+
totalBytesIn: 0,
|
|
651
|
+
totalBytesOut: 0,
|
|
652
|
+
eventCounts: {}
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
function generateSessionId() {
|
|
656
|
+
return `sess_${nanoid.nanoid(18)}`;
|
|
657
|
+
}
|
|
658
|
+
function generateEventId(sequence) {
|
|
659
|
+
return `evt_${String(sequence).padStart(4, "0")}_${nanoid.nanoid(4)}`;
|
|
660
|
+
}
|
|
661
|
+
function defaultEventLabel(type) {
|
|
662
|
+
switch (type) {
|
|
663
|
+
case "ai_call":
|
|
664
|
+
return "Assistant Response";
|
|
665
|
+
case "tool_call":
|
|
666
|
+
return "Tool Execution";
|
|
667
|
+
case "session":
|
|
668
|
+
return "Session Event";
|
|
669
|
+
default:
|
|
670
|
+
return type.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function ensureUniqueSections(eventId, sections) {
|
|
674
|
+
if (!sections || sections.length === 0) return void 0;
|
|
675
|
+
const labelCounts = /* @__PURE__ */ new Map();
|
|
676
|
+
const normalized = [];
|
|
677
|
+
let counter = 1;
|
|
678
|
+
for (const section of sections) {
|
|
679
|
+
const baseLabel = section.label?.trim().length ? section.label.trim() : defaultSectionLabel(section.kind);
|
|
680
|
+
const nextCount = (labelCounts.get(baseLabel) || 0) + 1;
|
|
681
|
+
labelCounts.set(baseLabel, nextCount);
|
|
682
|
+
const finalLabel = nextCount > 1 ? `${baseLabel} (${nextCount})` : baseLabel;
|
|
683
|
+
let id = section.id?.trim();
|
|
684
|
+
if (!id) {
|
|
685
|
+
id = `${section.kind}-${eventId}-${String(counter).padStart(2, "0")}`;
|
|
686
|
+
}
|
|
687
|
+
counter += 1;
|
|
688
|
+
normalized.push({
|
|
689
|
+
...section,
|
|
690
|
+
id,
|
|
691
|
+
label: finalLabel
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
return normalized;
|
|
695
|
+
}
|
|
696
|
+
function defaultSectionLabel(kind) {
|
|
697
|
+
switch (kind) {
|
|
698
|
+
case "message":
|
|
699
|
+
return "Message";
|
|
700
|
+
case "tool_call":
|
|
701
|
+
return "Tool Call";
|
|
702
|
+
case "tool_result":
|
|
703
|
+
return "Tool Result";
|
|
704
|
+
case "tool_response":
|
|
705
|
+
return "Tool Response";
|
|
706
|
+
case "summary":
|
|
707
|
+
return "Summary";
|
|
708
|
+
case "metadata":
|
|
709
|
+
return "Details";
|
|
710
|
+
default:
|
|
711
|
+
return "Section";
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function buildMessageLabel(role, index) {
|
|
715
|
+
const normalized = role?.trim().length ? role.trim() : "message";
|
|
716
|
+
const base = `${normalized.charAt(0).toUpperCase()}${normalized.slice(1)} Message`;
|
|
717
|
+
return index > 1 ? `${base} #${index}` : base;
|
|
718
|
+
}
|
|
719
|
+
function toDisplayString(value) {
|
|
720
|
+
const sanitized = sanitizeTracePayload(value);
|
|
721
|
+
if (sanitized == null) return "";
|
|
722
|
+
if (typeof sanitized === "string") return sanitized;
|
|
723
|
+
if (Array.isArray(sanitized)) {
|
|
724
|
+
return sanitized.map((item) => toDisplayString(item)).filter((part) => part && part.trim().length > 0).join("\n");
|
|
725
|
+
}
|
|
726
|
+
if (typeof sanitized === "object") {
|
|
727
|
+
if (typeof sanitized.text === "string" && Object.keys(sanitized).length === 1) {
|
|
728
|
+
return sanitized.text;
|
|
729
|
+
}
|
|
730
|
+
if (typeof sanitized.content === "string") {
|
|
731
|
+
return sanitized.content;
|
|
732
|
+
}
|
|
733
|
+
try {
|
|
734
|
+
return JSON.stringify(sanitized, null, 2);
|
|
735
|
+
} catch {
|
|
736
|
+
return String(sanitized);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return String(sanitized);
|
|
740
|
+
}
|
|
741
|
+
function parseToolArguments(args) {
|
|
742
|
+
if (args == null) return void 0;
|
|
743
|
+
if (typeof args === "string") {
|
|
744
|
+
try {
|
|
745
|
+
return JSON.parse(args);
|
|
746
|
+
} catch {
|
|
747
|
+
return args;
|
|
347
748
|
}
|
|
348
749
|
}
|
|
349
|
-
return
|
|
750
|
+
return args;
|
|
350
751
|
}
|
|
351
|
-
function
|
|
352
|
-
const { modelName, date, limits, usage, tools = [], messages } = params;
|
|
752
|
+
function collectToolCallSectionsFromMessage(message) {
|
|
353
753
|
const sections = [];
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
754
|
+
if (!message) return sections;
|
|
755
|
+
const addCall = (call) => {
|
|
756
|
+
if (!call) return;
|
|
757
|
+
const toolName = call?.name || call?.tool || call?.function?.name || call?.function_call?.name || "tool";
|
|
758
|
+
const rawArgs = call?.arguments ?? call?.args ?? call?.input ?? call?.function?.arguments ?? call?.function_call?.arguments;
|
|
759
|
+
const parsedArgs = parseToolArguments(rawArgs);
|
|
760
|
+
const argumentsPayload = sanitizeTracePayload(parsedArgs ?? rawArgs ?? {});
|
|
761
|
+
sections.push({
|
|
762
|
+
kind: "tool_call",
|
|
763
|
+
label: `Tool Call: ${toolName}`,
|
|
764
|
+
tool: toolName,
|
|
765
|
+
arguments: argumentsPayload
|
|
766
|
+
});
|
|
767
|
+
};
|
|
768
|
+
const directCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : void 0;
|
|
769
|
+
if (directCalls) {
|
|
770
|
+
directCalls.forEach((call) => addCall(call));
|
|
771
|
+
}
|
|
772
|
+
return sections;
|
|
773
|
+
}
|
|
774
|
+
function convertMessageListToSections(messageList) {
|
|
775
|
+
if (!Array.isArray(messageList) || messageList.length === 0) return void 0;
|
|
776
|
+
const sections = [];
|
|
777
|
+
const roleCounts = /* @__PURE__ */ new Map();
|
|
778
|
+
for (const message of messageList) {
|
|
779
|
+
if (!message) continue;
|
|
780
|
+
const toolSections = collectToolCallSectionsFromMessage(message);
|
|
781
|
+
if (toolSections.length > 0) {
|
|
782
|
+
sections.push(...toolSections);
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
const role = typeof message?.role === "string" ? message.role : "assistant";
|
|
786
|
+
const next = (roleCounts.get(role) || 0) + 1;
|
|
787
|
+
roleCounts.set(role, next);
|
|
788
|
+
let content = toDisplayString(message?.content ?? "");
|
|
789
|
+
if (!content || !content.trim().length) {
|
|
790
|
+
if (toolSections.length > 0) {
|
|
791
|
+
content = "[tool call]";
|
|
792
|
+
} else if (typeof message?.content === "undefined" || message?.content === null) {
|
|
793
|
+
continue;
|
|
794
|
+
} else {
|
|
795
|
+
content = toDisplayString(sanitizeTracePayload(message?.content));
|
|
796
|
+
if (!content || !content.trim().length) continue;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
sections.push({
|
|
800
|
+
kind: "message",
|
|
801
|
+
label: buildMessageLabel(role, next),
|
|
802
|
+
role,
|
|
803
|
+
content,
|
|
804
|
+
metadata: typeof message?.name === "string" ? { name: message.name } : void 0
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
return sections.length > 0 ? sections : void 0;
|
|
808
|
+
}
|
|
809
|
+
function createTraceSession(opts) {
|
|
810
|
+
const cfg = withDefaults(opts.tracing);
|
|
811
|
+
if (!cfg.enabled) return void 0;
|
|
812
|
+
const sessionId = generateSessionId();
|
|
813
|
+
const runtime = {
|
|
814
|
+
sessionId,
|
|
815
|
+
startedAt: Date.now(),
|
|
816
|
+
resolvedConfig: cfg,
|
|
817
|
+
events: [],
|
|
818
|
+
summary: createEmptySummary(),
|
|
819
|
+
status: "in_progress",
|
|
820
|
+
errors: []
|
|
821
|
+
};
|
|
822
|
+
if (cfg.sink.type === "file") {
|
|
823
|
+
const baseDir = cfg.sink.baseDir;
|
|
824
|
+
ensureDir(baseDir);
|
|
825
|
+
const sessionDir = path__default.default.join(baseDir, sessionId);
|
|
826
|
+
ensureDir(sessionDir);
|
|
827
|
+
runtime.fileBaseDir = baseDir;
|
|
828
|
+
runtime.fileSessionDir = sessionDir;
|
|
829
|
+
}
|
|
830
|
+
return runtime;
|
|
831
|
+
}
|
|
832
|
+
function recordTraceEvent(session, event) {
|
|
833
|
+
if (!session) return void 0;
|
|
834
|
+
const sequence = session.events.length + 1;
|
|
835
|
+
const id = generateEventId(sequence);
|
|
836
|
+
const timestampIso = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
837
|
+
const status = event.status ?? "success";
|
|
838
|
+
const baseLabel = event.label?.trim().length ? event.label.trim() : defaultEventLabel(event.type);
|
|
839
|
+
const eventLabel = `${baseLabel} #${sequence}`;
|
|
840
|
+
const durationMs = event.durationMs !== void 0 ? Number(event.durationMs) : void 0;
|
|
841
|
+
if (!Number.isNaN(durationMs ?? NaN) && durationMs !== void 0) {
|
|
842
|
+
session.summary.totalDurationMs += durationMs;
|
|
843
|
+
}
|
|
844
|
+
const inputTokens = event.inputTokens !== void 0 ? Number(event.inputTokens) : void 0;
|
|
845
|
+
if (!Number.isNaN(inputTokens ?? NaN) && inputTokens !== void 0) {
|
|
846
|
+
session.summary.totalInputTokens += inputTokens;
|
|
847
|
+
}
|
|
848
|
+
const outputTokens = event.outputTokens !== void 0 ? Number(event.outputTokens) : void 0;
|
|
849
|
+
if (!Number.isNaN(outputTokens ?? NaN) && outputTokens !== void 0) {
|
|
850
|
+
session.summary.totalOutputTokens += outputTokens;
|
|
851
|
+
}
|
|
852
|
+
const cachedInputTokens = event.cachedInputTokens !== void 0 ? Number(event.cachedInputTokens) : void 0;
|
|
853
|
+
if (!Number.isNaN(cachedInputTokens ?? NaN) && cachedInputTokens !== void 0) {
|
|
854
|
+
session.summary.totalCachedInputTokens += cachedInputTokens;
|
|
855
|
+
}
|
|
856
|
+
const totalTokens = event.totalTokens !== void 0 ? Number(event.totalTokens) : void 0;
|
|
857
|
+
const requestBytes = event.requestBytes !== void 0 ? Number(event.requestBytes) : void 0;
|
|
858
|
+
if (!Number.isNaN(requestBytes ?? NaN) && requestBytes !== void 0) {
|
|
859
|
+
session.summary.totalBytesIn += requestBytes;
|
|
860
|
+
}
|
|
861
|
+
const responseBytes = event.responseBytes !== void 0 ? Number(event.responseBytes) : void 0;
|
|
862
|
+
if (!Number.isNaN(responseBytes ?? NaN) && responseBytes !== void 0) {
|
|
863
|
+
session.summary.totalBytesOut += responseBytes;
|
|
864
|
+
}
|
|
865
|
+
session.summary.eventCounts[event.type] = (session.summary.eventCounts[event.type] || 0) + 1;
|
|
866
|
+
let sections;
|
|
867
|
+
if (session.resolvedConfig.logData) {
|
|
868
|
+
const converted = convertMessageListToSections(event.messageList);
|
|
869
|
+
sections = ensureUniqueSections(id, converted);
|
|
870
|
+
}
|
|
871
|
+
const resolvedModel = event.model ?? inferModelFromMessages(event.messageList);
|
|
872
|
+
const resolvedProvider = event.provider ?? inferProviderFromMessages(event.messageList);
|
|
873
|
+
const isAiCall = event.type === "ai_call";
|
|
874
|
+
const record = {
|
|
875
|
+
sessionId: session.sessionId,
|
|
876
|
+
id,
|
|
877
|
+
type: event.type,
|
|
878
|
+
label: eventLabel,
|
|
879
|
+
sequence,
|
|
880
|
+
timestamp: timestampIso,
|
|
881
|
+
actor: event.actor,
|
|
882
|
+
status,
|
|
883
|
+
durationMs,
|
|
884
|
+
...isAiCall ? {
|
|
885
|
+
inputTokens: inputTokens ?? 0,
|
|
886
|
+
outputTokens: outputTokens ?? 0,
|
|
887
|
+
totalTokens: totalTokens ?? 0,
|
|
888
|
+
cachedInputTokens: cachedInputTokens ?? 0
|
|
889
|
+
} : {
|
|
890
|
+
...inputTokens !== void 0 && { inputTokens },
|
|
891
|
+
...outputTokens !== void 0 && { outputTokens },
|
|
892
|
+
...totalTokens !== void 0 && { totalTokens },
|
|
893
|
+
...cachedInputTokens !== void 0 && { cachedInputTokens }
|
|
894
|
+
},
|
|
895
|
+
requestBytes,
|
|
896
|
+
responseBytes,
|
|
897
|
+
model: resolvedModel,
|
|
898
|
+
provider: resolvedProvider,
|
|
899
|
+
toolExecutionId: event.toolExecutionId,
|
|
900
|
+
retryOf: event.retryOf,
|
|
901
|
+
error: event.error ?? (status === "error" ? { message: "Unknown error" } : void 0) ?? void 0,
|
|
902
|
+
data: sections ? { sections } : void 0,
|
|
903
|
+
debug: event.debug
|
|
904
|
+
};
|
|
905
|
+
if (status === "error") {
|
|
906
|
+
const errorInfo = event.error ?? { message: "Unknown error" };
|
|
907
|
+
session.errors.push({
|
|
908
|
+
eventId: id,
|
|
909
|
+
message: errorInfo.message,
|
|
910
|
+
stack: errorInfo.stack,
|
|
911
|
+
type: event.type,
|
|
912
|
+
timestamp: timestampIso
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
session.events.push(record);
|
|
916
|
+
const sink = session.resolvedConfig.sink;
|
|
917
|
+
if (sink.type === "custom" && typeof sink.onEvent === "function") {
|
|
918
|
+
try {
|
|
919
|
+
const cloned = JSON.parse(JSON.stringify(record));
|
|
920
|
+
const result = sink.onEvent(cloned);
|
|
921
|
+
if (result && typeof result.then === "function") {
|
|
922
|
+
result.then(void 0, (error) => {
|
|
923
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
924
|
+
console.warn("Trace custom sink onEvent rejected", error);
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
} catch (err) {
|
|
929
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
930
|
+
console.warn("Trace custom sink onEvent failed", err);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return record;
|
|
935
|
+
}
|
|
936
|
+
async function finalizeTraceSession(session, params = {}) {
|
|
937
|
+
if (!session) return void 0;
|
|
938
|
+
const endedAtMs = Date.now();
|
|
939
|
+
const endedAtIso = new Date(endedAtMs).toISOString();
|
|
940
|
+
const durationMs = endedAtMs - session.startedAt;
|
|
941
|
+
const startedAtIso = new Date(session.startedAt).toISOString();
|
|
942
|
+
const agentInfo = params.agentRuntime ? {
|
|
943
|
+
name: params.agentRuntime.name,
|
|
944
|
+
version: params.agentRuntime.version,
|
|
945
|
+
model: getModelName(params.agentRuntime.model),
|
|
946
|
+
provider: getProviderName(params.agentRuntime.model)
|
|
947
|
+
} : void 0;
|
|
948
|
+
if (params.error) {
|
|
949
|
+
session.errors.push({
|
|
950
|
+
eventId: "session",
|
|
951
|
+
message: params.error.message || "Unknown error",
|
|
952
|
+
stack: params.error.stack,
|
|
953
|
+
type: "session",
|
|
954
|
+
timestamp: endedAtIso
|
|
396
955
|
});
|
|
397
|
-
return;
|
|
398
956
|
}
|
|
399
|
-
const
|
|
400
|
-
|
|
957
|
+
const configSnapshot = buildConfigSnapshot(session);
|
|
958
|
+
const initialStatus = params.status ? params.status : session.errors.length > 0 ? "error" : "success";
|
|
959
|
+
const buildPayload = (status2) => ({
|
|
960
|
+
sessionId: session.sessionId,
|
|
961
|
+
startedAt: startedAtIso,
|
|
962
|
+
endedAt: endedAtIso,
|
|
963
|
+
durationMs,
|
|
964
|
+
agent: agentInfo,
|
|
965
|
+
config: configSnapshot,
|
|
966
|
+
summary: session.summary,
|
|
967
|
+
events: session.events,
|
|
968
|
+
status: status2,
|
|
969
|
+
errors: session.errors
|
|
970
|
+
});
|
|
971
|
+
const payloadForSink = buildPayload(initialStatus);
|
|
972
|
+
let sinkFailed = false;
|
|
973
|
+
const sink = session.resolvedConfig.sink;
|
|
974
|
+
if (sink.type === "cognipeer" || sink.type === "http") {
|
|
975
|
+
try {
|
|
976
|
+
const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
|
|
977
|
+
await postTraceSession(sink.url, headers, payloadForSink);
|
|
978
|
+
} catch (err) {
|
|
979
|
+
sinkFailed = true;
|
|
980
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
981
|
+
session.errors.push({
|
|
982
|
+
eventId: "sink",
|
|
983
|
+
message,
|
|
984
|
+
type: "sink",
|
|
985
|
+
timestamp: endedAtIso
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
const hadNonSinkErrors = session.errors.some((error) => error.type && error.type !== "sink");
|
|
990
|
+
let status = params.status ?? (hadNonSinkErrors ? "error" : "success");
|
|
991
|
+
if (status === "success" && sinkFailed) {
|
|
992
|
+
status = "partial";
|
|
993
|
+
}
|
|
994
|
+
let finalPayload = buildPayload(status);
|
|
995
|
+
if (sink.type === "file" && session.fileSessionDir) {
|
|
996
|
+
try {
|
|
997
|
+
const filePath = path__default.default.join(session.fileSessionDir, "trace.session.json");
|
|
998
|
+
await fs__default.default.promises.writeFile(filePath, JSON.stringify(finalPayload, null, 2), "utf8");
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
sinkFailed = true;
|
|
1001
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1002
|
+
session.errors.push({
|
|
1003
|
+
eventId: "sink",
|
|
1004
|
+
message,
|
|
1005
|
+
type: "sink",
|
|
1006
|
+
timestamp: endedAtIso
|
|
1007
|
+
});
|
|
1008
|
+
if (status === "success") {
|
|
1009
|
+
status = "partial";
|
|
1010
|
+
finalPayload = buildPayload(status);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (sink.type === "custom" && typeof sink.onSession === "function") {
|
|
1015
|
+
try {
|
|
1016
|
+
const cloned = JSON.parse(JSON.stringify(finalPayload));
|
|
1017
|
+
await sink.onSession(cloned);
|
|
1018
|
+
} catch (err) {
|
|
1019
|
+
sinkFailed = true;
|
|
1020
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1021
|
+
session.errors.push({
|
|
1022
|
+
eventId: "sink",
|
|
1023
|
+
message,
|
|
1024
|
+
type: "sink",
|
|
1025
|
+
timestamp: endedAtIso
|
|
1026
|
+
});
|
|
1027
|
+
if (status === "success") {
|
|
1028
|
+
status = "partial";
|
|
1029
|
+
finalPayload = buildPayload(status);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (!params.status) {
|
|
1034
|
+
const finalHadNonSinkErrors = session.errors.some((error) => error.type && error.type !== "sink");
|
|
1035
|
+
const hasSinkErrors = session.errors.some((error) => error.type === "sink");
|
|
1036
|
+
let computedStatus = finalHadNonSinkErrors ? "error" : "success";
|
|
1037
|
+
if (computedStatus === "success" && hasSinkErrors) {
|
|
1038
|
+
computedStatus = "partial";
|
|
1039
|
+
}
|
|
1040
|
+
if (computedStatus !== status) {
|
|
1041
|
+
status = computedStatus;
|
|
1042
|
+
finalPayload = buildPayload(status);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return finalPayload;
|
|
1046
|
+
}
|
|
1047
|
+
function sanitizeTracePayload(value) {
|
|
1048
|
+
try {
|
|
1049
|
+
const cache = /* @__PURE__ */ new WeakSet();
|
|
1050
|
+
const json = JSON.stringify(
|
|
1051
|
+
value,
|
|
1052
|
+
(_key, val) => {
|
|
1053
|
+
if (typeof val === "function") return `[Function ${val.name || "anonymous"}]`;
|
|
1054
|
+
if (typeof val === "bigint") return val.toString();
|
|
1055
|
+
if (val instanceof Error) return { message: val.message, stack: val.stack };
|
|
1056
|
+
if (val && typeof val === "object") {
|
|
1057
|
+
if (cache.has(val)) return "[Circular]";
|
|
1058
|
+
cache.add(val);
|
|
1059
|
+
}
|
|
1060
|
+
return val;
|
|
1061
|
+
},
|
|
1062
|
+
2
|
|
1063
|
+
);
|
|
1064
|
+
return JSON.parse(json);
|
|
1065
|
+
} catch {
|
|
1066
|
+
try {
|
|
1067
|
+
return typeof value === "string" ? value : String(value);
|
|
1068
|
+
} catch {
|
|
1069
|
+
return "[Unserializable]";
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function estimatePayloadBytes(value) {
|
|
1074
|
+
try {
|
|
1075
|
+
const json = JSON.stringify(value);
|
|
1076
|
+
return buffer.Buffer.byteLength(json ?? "", "utf8");
|
|
1077
|
+
} catch {
|
|
1078
|
+
try {
|
|
1079
|
+
return buffer.Buffer.byteLength(String(value ?? ""), "utf8");
|
|
1080
|
+
} catch {
|
|
1081
|
+
return 0;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
401
1084
|
}
|
|
402
1085
|
|
|
403
1086
|
// src/nodes/agentCore.ts
|
|
@@ -405,16 +1088,91 @@ function createAgentCoreNode(opts) {
|
|
|
405
1088
|
return async (state) => {
|
|
406
1089
|
const runtime = state.agent || {
|
|
407
1090
|
name: opts.name,
|
|
1091
|
+
version: opts.version,
|
|
408
1092
|
model: opts.model,
|
|
409
1093
|
tools: opts.tools || [],
|
|
410
|
-
|
|
1094
|
+
guardrails: opts.guardrails,
|
|
411
1095
|
limits: opts.limits,
|
|
412
|
-
|
|
413
|
-
|
|
1096
|
+
outputSchema: opts.outputSchema,
|
|
1097
|
+
tracing: opts.tracing
|
|
414
1098
|
};
|
|
415
1099
|
const tools = runtime.tools ?? [];
|
|
416
1100
|
const modelWithTools = runtime.model?.bindTools ? runtime.model.bindTools(tools) : runtime.model;
|
|
417
|
-
const
|
|
1101
|
+
const traceSession = state.ctx?.__traceSession;
|
|
1102
|
+
const actorName = runtime.name ?? opts.name ?? "agent";
|
|
1103
|
+
const actorVersion = runtime.version ?? opts.version;
|
|
1104
|
+
const start = Date.now();
|
|
1105
|
+
const shouldLogPrompt = !!traceSession && traceSession.resolvedConfig.logData;
|
|
1106
|
+
const promptPayload = shouldLogPrompt ? sanitizeTracePayload(state.messages) : void 0;
|
|
1107
|
+
const promptBytes = promptPayload !== void 0 ? estimatePayloadBytes(promptPayload) : void 0;
|
|
1108
|
+
const extractToolUses = (m) => {
|
|
1109
|
+
const tcs = m?.tool_calls || m?.additional_kwargs?.tool_calls;
|
|
1110
|
+
if (!Array.isArray(tcs)) return [];
|
|
1111
|
+
return tcs.map((tc) => ({ id: tc?.id, name: tc?.function?.name || tc?.name })).filter((x) => typeof x.id === "string" && x.id.length > 0);
|
|
1112
|
+
};
|
|
1113
|
+
const normalizeBedrockToolPairing = (input) => {
|
|
1114
|
+
const msgs = Array.isArray(input) ? [...input] : [];
|
|
1115
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
1116
|
+
const m = msgs[i];
|
|
1117
|
+
if (m?.role !== "assistant") continue;
|
|
1118
|
+
const toolUses = extractToolUses(m);
|
|
1119
|
+
if (toolUses.length === 0) continue;
|
|
1120
|
+
const next = msgs[i + 1];
|
|
1121
|
+
const firstId = toolUses[0]?.id;
|
|
1122
|
+
const nextIsToolForFirst = !!(next && next.role === "tool" && next.tool_call_id === firstId);
|
|
1123
|
+
if (nextIsToolForFirst) continue;
|
|
1124
|
+
const placeholders = toolUses.map((tu) => ({
|
|
1125
|
+
role: "tool",
|
|
1126
|
+
name: tu.name || "unknown_tool",
|
|
1127
|
+
tool_call_id: tu.id,
|
|
1128
|
+
content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for Bedrock tool_result adjacency."
|
|
1129
|
+
}));
|
|
1130
|
+
msgs.splice(i + 1, 0, ...placeholders);
|
|
1131
|
+
i += placeholders.length;
|
|
1132
|
+
}
|
|
1133
|
+
return msgs;
|
|
1134
|
+
};
|
|
1135
|
+
const debugToolPairing = (messagesToSend) => {
|
|
1136
|
+
try {
|
|
1137
|
+
const msgs = Array.isArray(messagesToSend) ? messagesToSend : [];
|
|
1138
|
+
const toolUseIds = [];
|
|
1139
|
+
const missingResults = [];
|
|
1140
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
1141
|
+
const m = msgs[i];
|
|
1142
|
+
if (m?.role === "assistant") {
|
|
1143
|
+
for (const tu of extractToolUses(m)) {
|
|
1144
|
+
toolUseIds.push(tu.id);
|
|
1145
|
+
const next = msgs[i + 1];
|
|
1146
|
+
if (!(next && next.role === "tool" && (next.tool_call_id === tu.id || next.tool_call_id === m?.tool_call_id))) {
|
|
1147
|
+
missingResults.push({ id: tu.id, atIndex: i, tool: tu.name });
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
} catch (e) {
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
let response;
|
|
1156
|
+
try {
|
|
1157
|
+
const normalizedMessages = normalizeBedrockToolPairing([...state.messages]);
|
|
1158
|
+
debugToolPairing(normalizedMessages);
|
|
1159
|
+
response = await modelWithTools.invoke(normalizedMessages);
|
|
1160
|
+
} catch (err) {
|
|
1161
|
+
const durationMs2 = Date.now() - start;
|
|
1162
|
+
recordTraceEvent(traceSession, {
|
|
1163
|
+
type: "ai_call",
|
|
1164
|
+
label: "Assistant Error",
|
|
1165
|
+
actor: { scope: "agent", name: actorName, role: "assistant", version: actorVersion },
|
|
1166
|
+
status: "error",
|
|
1167
|
+
durationMs: durationMs2,
|
|
1168
|
+
requestBytes: promptBytes,
|
|
1169
|
+
model: getModelName(runtime.model || opts.model),
|
|
1170
|
+
provider: getProviderName(runtime.model || opts.model),
|
|
1171
|
+
error: { message: err?.message || String(err), stack: err?.stack },
|
|
1172
|
+
messageList: state.messages
|
|
1173
|
+
});
|
|
1174
|
+
throw err;
|
|
1175
|
+
}
|
|
418
1176
|
const messagesWithResponse = [
|
|
419
1177
|
...state.messages,
|
|
420
1178
|
response
|
|
@@ -422,6 +1180,25 @@ function createAgentCoreNode(opts) {
|
|
|
422
1180
|
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.usage;
|
|
423
1181
|
const normalized = normalizeUsage(rawUsage);
|
|
424
1182
|
const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
|
|
1183
|
+
const durationMs = Date.now() - start;
|
|
1184
|
+
const shouldLogResponse = !!traceSession && traceSession.resolvedConfig.logData;
|
|
1185
|
+
const responsePayload = shouldLogResponse ? sanitizeTracePayload(response) : void 0;
|
|
1186
|
+
const responseBytes = responsePayload !== void 0 ? estimatePayloadBytes(responsePayload) : void 0;
|
|
1187
|
+
recordTraceEvent(traceSession, {
|
|
1188
|
+
type: "ai_call",
|
|
1189
|
+
label: "Assistant Response",
|
|
1190
|
+
actor: { scope: "agent", name: actorName, role: "assistant", version: actorVersion },
|
|
1191
|
+
durationMs,
|
|
1192
|
+
inputTokens: normalized?.prompt_tokens,
|
|
1193
|
+
outputTokens: normalized?.completion_tokens,
|
|
1194
|
+
totalTokens: normalized?.total_tokens,
|
|
1195
|
+
cachedInputTokens: normalized?.prompt_tokens_details?.cached_tokens,
|
|
1196
|
+
requestBytes: promptBytes,
|
|
1197
|
+
responseBytes,
|
|
1198
|
+
model: modelName,
|
|
1199
|
+
provider: getProviderName(runtime.model || opts.model),
|
|
1200
|
+
messageList: messagesWithResponse
|
|
1201
|
+
});
|
|
425
1202
|
if (normalized) {
|
|
426
1203
|
const usageState = state.usage || { perRequest: [], totals: {} };
|
|
427
1204
|
const id = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -445,10 +1222,6 @@ function createAgentCoreNode(opts) {
|
|
|
445
1222
|
return { messages: messagesWithResponse, usage: state.usage };
|
|
446
1223
|
};
|
|
447
1224
|
}
|
|
448
|
-
|
|
449
|
-
// src/nodes/tools.ts
|
|
450
|
-
var import_nanoid = require("nanoid");
|
|
451
|
-
var import_messages = require("@langchain/core/messages");
|
|
452
1225
|
function createToolsNode(initialTools, opts) {
|
|
453
1226
|
const baseToolByName = /* @__PURE__ */ new Map();
|
|
454
1227
|
for (const t of initialTools) baseToolByName.set(t.name, t);
|
|
@@ -471,6 +1244,10 @@ function createToolsNode(initialTools, opts) {
|
|
|
471
1244
|
};
|
|
472
1245
|
const appended = [];
|
|
473
1246
|
const onEvent = state.ctx?.__onEvent;
|
|
1247
|
+
const traceSession = state.ctx?.__traceSession;
|
|
1248
|
+
const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
|
|
1249
|
+
const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
|
|
1250
|
+
let awaitingApproval = false;
|
|
474
1251
|
for (const t of toolByName.values()) {
|
|
475
1252
|
const anyT = t;
|
|
476
1253
|
if (anyT._stateRef && typeof anyT._stateRef === "object") {
|
|
@@ -490,25 +1267,51 @@ function createToolsNode(initialTools, opts) {
|
|
|
490
1267
|
const skipped = toolCalls.slice(remaining);
|
|
491
1268
|
for (const tc of skipped) {
|
|
492
1269
|
onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
|
|
493
|
-
|
|
1270
|
+
const sanitizedArgs = sanitizeTracePayload(tc.args);
|
|
1271
|
+
const messageList = [
|
|
1272
|
+
{
|
|
1273
|
+
role: "assistant",
|
|
1274
|
+
name: tc.name,
|
|
1275
|
+
content: `Skipped tool due to tool-call limit: ${tc.name}`,
|
|
1276
|
+
tool_calls: [
|
|
1277
|
+
{
|
|
1278
|
+
id: tc.id,
|
|
1279
|
+
type: "function",
|
|
1280
|
+
function: {
|
|
1281
|
+
name: tc.name,
|
|
1282
|
+
arguments: sanitizedArgs
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
]
|
|
1286
|
+
}
|
|
1287
|
+
];
|
|
1288
|
+
recordTraceEvent(traceSession, {
|
|
1289
|
+
type: "tool_call",
|
|
1290
|
+
label: `Tool Skipped - ${tc.name}`,
|
|
1291
|
+
actor: { scope: "tool", name: tc.name, role: "tool" },
|
|
1292
|
+
status: "skipped",
|
|
1293
|
+
toolExecutionId: tc.id,
|
|
1294
|
+
messageList
|
|
1295
|
+
});
|
|
1296
|
+
appended.push({
|
|
1297
|
+
role: "tool",
|
|
494
1298
|
content: `Skipped tool due to tool-call limit: ${tc.name}`,
|
|
495
1299
|
tool_call_id: tc.id || `${tc.name}_${appended.length}`,
|
|
496
1300
|
name: tc.name
|
|
497
|
-
})
|
|
1301
|
+
});
|
|
498
1302
|
}
|
|
499
1303
|
const runOne = async (tc) => {
|
|
500
1304
|
const t = toolByName.get(tc.name);
|
|
501
1305
|
if (!t) {
|
|
502
1306
|
onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
|
|
503
|
-
appended.push(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
);
|
|
1307
|
+
appended.push({
|
|
1308
|
+
role: "tool",
|
|
1309
|
+
content: `Tool not found: ${tc.name}`,
|
|
1310
|
+
tool_call_id: tc.id || `${tc.name}_${appended.length}`,
|
|
1311
|
+
name: tc.name
|
|
1312
|
+
});
|
|
510
1313
|
toolCount += 1;
|
|
511
|
-
return;
|
|
1314
|
+
return { status: "error" };
|
|
512
1315
|
}
|
|
513
1316
|
let args = tc.args;
|
|
514
1317
|
if (typeof args === "string") {
|
|
@@ -517,9 +1320,67 @@ function createToolsNode(initialTools, opts) {
|
|
|
517
1320
|
} catch (_) {
|
|
518
1321
|
}
|
|
519
1322
|
}
|
|
1323
|
+
const toolCallId = tc.id || `${tc.name}_${toolCount + 1}`;
|
|
1324
|
+
let approvalEntry = pendingByCallId.get(toolCallId);
|
|
1325
|
+
const needsApproval = Boolean(t.needsApproval);
|
|
1326
|
+
if (needsApproval) {
|
|
1327
|
+
if (!approvalEntry) {
|
|
1328
|
+
approvalEntry = {
|
|
1329
|
+
id: nanoid.nanoid(),
|
|
1330
|
+
toolCallId,
|
|
1331
|
+
toolName: t.name || tc.name,
|
|
1332
|
+
args,
|
|
1333
|
+
status: "pending",
|
|
1334
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1335
|
+
metadata: t.approvalPrompt || t.approvalDefaults ? { prompt: t.approvalPrompt, defaults: t.approvalDefaults } : void 0
|
|
1336
|
+
};
|
|
1337
|
+
pendingApprovals.push(approvalEntry);
|
|
1338
|
+
pendingByCallId.set(toolCallId, approvalEntry);
|
|
1339
|
+
onEvent?.({ type: "tool_approval", status: "pending", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, args });
|
|
1340
|
+
} else if (!approvalEntry.args) {
|
|
1341
|
+
approvalEntry.args = args;
|
|
1342
|
+
}
|
|
1343
|
+
if (approvalEntry.status === "pending") {
|
|
1344
|
+
awaitingApproval = true;
|
|
1345
|
+
const ctx = state.ctx = state.ctx || {};
|
|
1346
|
+
ctx.__awaitingApproval = {
|
|
1347
|
+
approvalId: approvalEntry.id,
|
|
1348
|
+
toolCallId: approvalEntry.toolCallId,
|
|
1349
|
+
toolName: approvalEntry.toolName,
|
|
1350
|
+
requestedAt: approvalEntry.requestedAt
|
|
1351
|
+
};
|
|
1352
|
+
ctx.__resumeStage = "tools";
|
|
1353
|
+
return { status: "awaiting_approval", approval: approvalEntry };
|
|
1354
|
+
}
|
|
1355
|
+
if (approvalEntry.status === "rejected") {
|
|
1356
|
+
const rejectionMessage = approvalEntry.comment || "Tool call rejected by reviewer.";
|
|
1357
|
+
appended.push({
|
|
1358
|
+
role: "tool",
|
|
1359
|
+
content: `Tool call rejected: ${rejectionMessage}`,
|
|
1360
|
+
tool_call_id: toolCallId,
|
|
1361
|
+
name: tc.name
|
|
1362
|
+
});
|
|
1363
|
+
onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
|
|
1364
|
+
approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
|
|
1365
|
+
approvalEntry.status = "executed";
|
|
1366
|
+
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1367
|
+
toolHistory.push({ executionId: nanoid.nanoid(), toolName: t.name, args, output: `Rejected: ${rejectionMessage}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
|
|
1368
|
+
pendingByCallId.set(toolCallId, approvalEntry);
|
|
1369
|
+
toolCount += 1;
|
|
1370
|
+
return { status: "rejected", approval: approvalEntry };
|
|
1371
|
+
}
|
|
1372
|
+
if (approvalEntry.status === "approved") {
|
|
1373
|
+
if (approvalEntry.approvedArgs !== void 0) {
|
|
1374
|
+
args = approvalEntry.approvedArgs;
|
|
1375
|
+
}
|
|
1376
|
+
onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
const start = Date.now();
|
|
520
1380
|
try {
|
|
521
|
-
const start = Date.now();
|
|
522
1381
|
onEvent?.({ type: "tool_call", phase: "start", name: t.name, id: tc.id, args });
|
|
1382
|
+
const sanitizedArgs = sanitizeTracePayload(args);
|
|
1383
|
+
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
523
1384
|
let output;
|
|
524
1385
|
const anyTool = t;
|
|
525
1386
|
if (typeof anyTool.func === "function") output = await anyTool.func(args);
|
|
@@ -530,14 +1391,20 @@ function createToolsNode(initialTools, opts) {
|
|
|
530
1391
|
else throw new Error("Tool is not invokable");
|
|
531
1392
|
if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
|
|
532
1393
|
const newRuntime = output.__handoff.runtime;
|
|
533
|
-
const executionId2 =
|
|
1394
|
+
const executionId2 = nanoid.nanoid();
|
|
534
1395
|
toolHistory.push({ executionId: executionId2, toolName: t.name, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
|
|
535
|
-
appended.push(
|
|
1396
|
+
appended.push({ role: "tool", content: "ok", tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
536
1397
|
onEvent?.({ type: "handoff", from: runtime.name, to: newRuntime.name, toolName: t.name });
|
|
537
1398
|
state.agent = newRuntime;
|
|
538
1399
|
onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: "handoff", durationMs: Date.now() - start });
|
|
539
1400
|
toolCount += 1;
|
|
540
|
-
|
|
1401
|
+
if (needsApproval && approvalEntry) {
|
|
1402
|
+
approvalEntry.status = "executed";
|
|
1403
|
+
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1404
|
+
approvalEntry.executionId = executionId2;
|
|
1405
|
+
pendingByCallId.set(toolCallId, approvalEntry);
|
|
1406
|
+
}
|
|
1407
|
+
return { status: "success", approval: approvalEntry };
|
|
541
1408
|
}
|
|
542
1409
|
const content = typeof output === "string" ? output : JSON.stringify(output);
|
|
543
1410
|
if (output && typeof output === "object" && output.__finalStructuredOutput) {
|
|
@@ -545,25 +1412,124 @@ function createToolsNode(initialTools, opts) {
|
|
|
545
1412
|
state.ctx.__structuredOutputParsed = output.data;
|
|
546
1413
|
state.ctx.__finalizedDueToStructuredOutput = true;
|
|
547
1414
|
}
|
|
548
|
-
const
|
|
1415
|
+
const durationMs = Date.now() - start;
|
|
1416
|
+
const executionId = nanoid.nanoid();
|
|
549
1417
|
toolHistory.push({ executionId, toolName: t.name, args, output, rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
|
|
550
|
-
appended.push(
|
|
551
|
-
onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: output, durationMs
|
|
1418
|
+
appended.push({ role: "tool", content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1419
|
+
onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: output, durationMs });
|
|
1420
|
+
if (needsApproval && approvalEntry) {
|
|
1421
|
+
approvalEntry.status = "executed";
|
|
1422
|
+
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1423
|
+
approvalEntry.executionId = executionId;
|
|
1424
|
+
pendingByCallId.set(toolCallId, approvalEntry);
|
|
1425
|
+
}
|
|
1426
|
+
const sanitizedOutput = sanitizeTracePayload(output);
|
|
1427
|
+
const outputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedOutput) : void 0;
|
|
1428
|
+
const toolName = t.name || tc.name;
|
|
1429
|
+
const messageList = [
|
|
1430
|
+
{
|
|
1431
|
+
role: "assistant",
|
|
1432
|
+
name: toolName,
|
|
1433
|
+
content: "",
|
|
1434
|
+
tool_calls: [
|
|
1435
|
+
{
|
|
1436
|
+
id: tc.id || executionId,
|
|
1437
|
+
type: "function",
|
|
1438
|
+
function: {
|
|
1439
|
+
name: toolName,
|
|
1440
|
+
arguments: sanitizedArgs
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
]
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
role: "tool",
|
|
1447
|
+
name: toolName,
|
|
1448
|
+
content: sanitizedOutput ?? output ?? ""
|
|
1449
|
+
}
|
|
1450
|
+
];
|
|
1451
|
+
recordTraceEvent(traceSession, {
|
|
1452
|
+
type: "tool_call",
|
|
1453
|
+
label: `Tool Execution - ${toolName}`,
|
|
1454
|
+
actor: { scope: "tool", name: toolName, role: "tool" },
|
|
1455
|
+
durationMs,
|
|
1456
|
+
requestBytes: inputBytes,
|
|
1457
|
+
responseBytes: outputBytes,
|
|
1458
|
+
toolExecutionId: executionId,
|
|
1459
|
+
messageList
|
|
1460
|
+
});
|
|
552
1461
|
toolCount += 1;
|
|
1462
|
+
return { status: "success", approval: approvalEntry };
|
|
553
1463
|
} catch (e) {
|
|
554
|
-
const
|
|
1464
|
+
const durationMs = Date.now() - start;
|
|
1465
|
+
const executionId = nanoid.nanoid();
|
|
555
1466
|
toolHistory.push({ executionId, toolName: t.name, args, output: `Error executing tool: ${e?.message || String(e)}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
|
|
556
|
-
appended.push(
|
|
1467
|
+
appended.push({ role: "tool", content: `Error executing tool: ${e?.message || String(e)}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
557
1468
|
onEvent?.({ type: "tool_call", phase: "error", name: t.name, id: tc.id, args, error: { message: e?.message || String(e) } });
|
|
1469
|
+
const sanitizedArgs = sanitizeTracePayload(args);
|
|
1470
|
+
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1471
|
+
const toolName = t.name || tc.name;
|
|
1472
|
+
const errorMessage = e?.message || String(e);
|
|
1473
|
+
const messageList = [
|
|
1474
|
+
{
|
|
1475
|
+
role: "assistant",
|
|
1476
|
+
name: toolName,
|
|
1477
|
+
content: "",
|
|
1478
|
+
tool_calls: [
|
|
1479
|
+
{
|
|
1480
|
+
id: tc.id || executionId,
|
|
1481
|
+
type: "function",
|
|
1482
|
+
function: {
|
|
1483
|
+
name: toolName,
|
|
1484
|
+
arguments: sanitizedArgs
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
]
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
role: "tool",
|
|
1491
|
+
name: toolName,
|
|
1492
|
+
content: `Error executing tool: ${errorMessage}`
|
|
1493
|
+
}
|
|
1494
|
+
];
|
|
1495
|
+
recordTraceEvent(traceSession, {
|
|
1496
|
+
type: "tool_call",
|
|
1497
|
+
label: `Tool Error - ${toolName}`,
|
|
1498
|
+
actor: { scope: "tool", name: toolName, role: "tool" },
|
|
1499
|
+
status: "error",
|
|
1500
|
+
durationMs,
|
|
1501
|
+
requestBytes: inputBytes,
|
|
1502
|
+
toolExecutionId: executionId,
|
|
1503
|
+
error: { message: e?.message || String(e), stack: e?.stack },
|
|
1504
|
+
messageList
|
|
1505
|
+
});
|
|
558
1506
|
toolCount += 1;
|
|
1507
|
+
return { status: "error", approval: approvalEntry };
|
|
559
1508
|
}
|
|
560
1509
|
};
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
1510
|
+
for (const tc of planned) {
|
|
1511
|
+
if (awaitingApproval) break;
|
|
1512
|
+
const result = await runOne(tc);
|
|
1513
|
+
if (result?.status === "awaiting_approval") {
|
|
1514
|
+
awaitingApproval = true;
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
if (!awaitingApproval && state.ctx?.__awaitingApproval) {
|
|
1519
|
+
const ctx = { ...state.ctx };
|
|
1520
|
+
delete ctx.__awaitingApproval;
|
|
1521
|
+
if (!pendingApprovals.some((entry) => entry.status !== "executed")) {
|
|
1522
|
+
delete ctx.__resumeStage;
|
|
1523
|
+
}
|
|
1524
|
+
state.ctx = Object.keys(ctx).length > 0 ? ctx : void 0;
|
|
1525
|
+
}
|
|
1526
|
+
return {
|
|
1527
|
+
messages: [...state.messages, ...appended],
|
|
1528
|
+
toolCallCount: toolCount,
|
|
1529
|
+
toolHistory,
|
|
1530
|
+
agent: state.agent,
|
|
1531
|
+
pendingApprovals
|
|
1532
|
+
};
|
|
567
1533
|
};
|
|
568
1534
|
}
|
|
569
1535
|
|
|
@@ -575,6 +1541,213 @@ function createToolLimitFinalizeNode(_opts) {
|
|
|
575
1541
|
return { messages: [...state.messages, notice], ctx };
|
|
576
1542
|
};
|
|
577
1543
|
}
|
|
1544
|
+
function ensureArray(value) {
|
|
1545
|
+
return Array.isArray(value) ? value : [];
|
|
1546
|
+
}
|
|
1547
|
+
function buildContext(params) {
|
|
1548
|
+
const latest = params.state.messages[params.state.messages.length - 1];
|
|
1549
|
+
return {
|
|
1550
|
+
phase: params.phase,
|
|
1551
|
+
messages: params.state.messages,
|
|
1552
|
+
latestMessage: latest,
|
|
1553
|
+
state: params.state,
|
|
1554
|
+
runtime: params.runtime,
|
|
1555
|
+
options: params.options
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
function normalizeDisposition(passed, disposition) {
|
|
1559
|
+
if (disposition) return disposition;
|
|
1560
|
+
return passed ? "allow" : "block";
|
|
1561
|
+
}
|
|
1562
|
+
async function evaluateGuardrails(params) {
|
|
1563
|
+
const incidents = [];
|
|
1564
|
+
const guardrails = ensureArray(params.guardrails).filter(
|
|
1565
|
+
(guardrail) => ensureArray(guardrail.appliesTo).includes(params.phase)
|
|
1566
|
+
);
|
|
1567
|
+
if (guardrails.length === 0) {
|
|
1568
|
+
return { ok: true, incidents };
|
|
1569
|
+
}
|
|
1570
|
+
const context = buildContext(params);
|
|
1571
|
+
for (const guardrail of guardrails) {
|
|
1572
|
+
for (const rule of ensureArray(guardrail.rules)) {
|
|
1573
|
+
let result;
|
|
1574
|
+
try {
|
|
1575
|
+
result = await rule.evaluate(context);
|
|
1576
|
+
} catch (err) {
|
|
1577
|
+
result = {
|
|
1578
|
+
passed: false,
|
|
1579
|
+
reason: err instanceof Error ? err.message : String(err ?? "Unknown error"),
|
|
1580
|
+
details: { thrown: true },
|
|
1581
|
+
disposition: "block"
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
const passed = result?.passed !== false;
|
|
1585
|
+
const disposition = normalizeDisposition(passed, result?.disposition);
|
|
1586
|
+
if (passed && disposition === "allow") {
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
const incident = {
|
|
1590
|
+
guardrailId: guardrail.id ?? rule.id ?? nanoid.nanoid(8),
|
|
1591
|
+
guardrailTitle: guardrail.title,
|
|
1592
|
+
ruleId: rule.id,
|
|
1593
|
+
ruleTitle: rule.title,
|
|
1594
|
+
phase: params.phase,
|
|
1595
|
+
reason: result?.reason,
|
|
1596
|
+
details: result?.details,
|
|
1597
|
+
disposition
|
|
1598
|
+
};
|
|
1599
|
+
let effectiveDisposition = incident.disposition;
|
|
1600
|
+
if (typeof guardrail.onViolation === "function") {
|
|
1601
|
+
try {
|
|
1602
|
+
const override = await guardrail.onViolation(incident, context);
|
|
1603
|
+
if (override) {
|
|
1604
|
+
effectiveDisposition = override;
|
|
1605
|
+
incident.disposition = override;
|
|
1606
|
+
}
|
|
1607
|
+
} catch (err) {
|
|
1608
|
+
effectiveDisposition = "block";
|
|
1609
|
+
incident.disposition = "block";
|
|
1610
|
+
incident.details = {
|
|
1611
|
+
...incident.details || {},
|
|
1612
|
+
onViolationError: err instanceof Error ? err.message : String(err ?? "Unknown error")
|
|
1613
|
+
};
|
|
1614
|
+
if (!incident.reason) {
|
|
1615
|
+
incident.reason = "Guardrail onViolation handler threw an error.";
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
incidents.push(incident);
|
|
1620
|
+
params.emit?.({
|
|
1621
|
+
type: "guardrail",
|
|
1622
|
+
phase: params.phase,
|
|
1623
|
+
guardrailId: incident.guardrailId,
|
|
1624
|
+
guardrailTitle: incident.guardrailTitle,
|
|
1625
|
+
ruleId: incident.ruleId,
|
|
1626
|
+
ruleTitle: incident.ruleTitle,
|
|
1627
|
+
disposition: effectiveDisposition,
|
|
1628
|
+
reason: incident.reason,
|
|
1629
|
+
details: incident.details
|
|
1630
|
+
});
|
|
1631
|
+
if (effectiveDisposition === "block" && guardrail.haltOnViolation !== false) {
|
|
1632
|
+
break;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
const ok = incidents.every((incident) => incident.disposition !== "block");
|
|
1637
|
+
return { ok, incidents };
|
|
1638
|
+
}
|
|
1639
|
+
function createGuardrail(options) {
|
|
1640
|
+
const appliesTo = options.appliesTo?.length ? options.appliesTo : ["request" /* Request */, "response" /* Response */];
|
|
1641
|
+
return {
|
|
1642
|
+
id: options.id,
|
|
1643
|
+
title: options.title,
|
|
1644
|
+
description: options.description,
|
|
1645
|
+
appliesTo,
|
|
1646
|
+
rules: options.checks,
|
|
1647
|
+
haltOnViolation: options.haltOnViolation,
|
|
1648
|
+
onViolation: options.onViolation,
|
|
1649
|
+
metadata: options.metadata
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// src/utils/stateSnapshot.ts
|
|
1654
|
+
var DISALLOWED_CTX_KEYS = /* @__PURE__ */ new Set(["__onEvent", "__traceSession", "__paused"]);
|
|
1655
|
+
var clone = (value) => {
|
|
1656
|
+
if (typeof globalThis.structuredClone === "function") {
|
|
1657
|
+
return globalThis.structuredClone(value);
|
|
1658
|
+
}
|
|
1659
|
+
return JSON.parse(JSON.stringify(value));
|
|
1660
|
+
};
|
|
1661
|
+
var cleanCtx = (ctx) => {
|
|
1662
|
+
if (!ctx) return void 0;
|
|
1663
|
+
const entries = Object.entries(ctx).filter(([key]) => !DISALLOWED_CTX_KEYS.has(key));
|
|
1664
|
+
if (entries.length === 0) return void 0;
|
|
1665
|
+
return Object.fromEntries(entries);
|
|
1666
|
+
};
|
|
1667
|
+
var runtimeHintFromAgent = (agent) => {
|
|
1668
|
+
if (!agent) return void 0;
|
|
1669
|
+
const tools = Array.isArray(agent.tools) ? agent.tools.map((tool) => tool?.name).filter(Boolean) : void 0;
|
|
1670
|
+
return {
|
|
1671
|
+
name: agent.name,
|
|
1672
|
+
version: agent.version,
|
|
1673
|
+
tools: tools && tools.length > 0 ? tools : void 0
|
|
1674
|
+
};
|
|
1675
|
+
};
|
|
1676
|
+
function captureSnapshot(state, options) {
|
|
1677
|
+
const { agent, ctx, ...rest } = state;
|
|
1678
|
+
const metadata = {
|
|
1679
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1680
|
+
tag: options?.tag,
|
|
1681
|
+
paused: ctx?.__paused ?? null
|
|
1682
|
+
};
|
|
1683
|
+
const safeState = clone({
|
|
1684
|
+
...rest,
|
|
1685
|
+
ctx: cleanCtx(ctx)
|
|
1686
|
+
});
|
|
1687
|
+
if (safeState.ctx && "__paused" in safeState.ctx) {
|
|
1688
|
+
delete safeState.ctx.__paused;
|
|
1689
|
+
}
|
|
1690
|
+
return {
|
|
1691
|
+
state: safeState,
|
|
1692
|
+
runtimeHint: options?.includeRuntimeHint !== false ? runtimeHintFromAgent(agent) : void 0,
|
|
1693
|
+
metadata
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
function restoreSnapshot(snapshot, restoreOptions) {
|
|
1697
|
+
const base = clone(snapshot.state);
|
|
1698
|
+
const incomingCtx = restoreOptions?.ctx;
|
|
1699
|
+
const mergedCtx = restoreOptions?.mergeCtx === false ? cleanCtx(incomingCtx) : {
|
|
1700
|
+
...base.ctx || {},
|
|
1701
|
+
...cleanCtx(incomingCtx)
|
|
1702
|
+
};
|
|
1703
|
+
const ctx = {
|
|
1704
|
+
...mergedCtx || {},
|
|
1705
|
+
__restoredFromSnapshot: true
|
|
1706
|
+
};
|
|
1707
|
+
const restored = {
|
|
1708
|
+
...base,
|
|
1709
|
+
ctx
|
|
1710
|
+
};
|
|
1711
|
+
if (restoreOptions?.agent) {
|
|
1712
|
+
restored.agent = restoreOptions.agent;
|
|
1713
|
+
}
|
|
1714
|
+
return restored;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/utils/toolApprovals.ts
|
|
1718
|
+
var cloneApprovals = (approvals) => Array.isArray(approvals) ? approvals.map((entry) => ({ ...entry })) : [];
|
|
1719
|
+
function resolveToolApprovalState(state, resolution) {
|
|
1720
|
+
const approvals = cloneApprovals(state.pendingApprovals);
|
|
1721
|
+
const matchIndex = approvals.findIndex((entry2) => entry2.id === resolution.id || entry2.toolCallId === resolution.id);
|
|
1722
|
+
if (matchIndex === -1) {
|
|
1723
|
+
throw new Error(`Pending approval not found for id: ${resolution.id}`);
|
|
1724
|
+
}
|
|
1725
|
+
const entry = { ...approvals[matchIndex] };
|
|
1726
|
+
if (entry.status === "executed") {
|
|
1727
|
+
throw new Error(`Tool approval ${entry.id} already completed.`);
|
|
1728
|
+
}
|
|
1729
|
+
entry.status = resolution.approved ? "approved" : "rejected";
|
|
1730
|
+
entry.approvedArgs = resolution.approved ? resolution.approvedArgs ?? entry.args : void 0;
|
|
1731
|
+
entry.decidedBy = resolution.decidedBy;
|
|
1732
|
+
entry.comment = resolution.comment;
|
|
1733
|
+
entry.decidedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1734
|
+
approvals[matchIndex] = entry;
|
|
1735
|
+
const ctx = { ...state.ctx || {} };
|
|
1736
|
+
delete ctx.__awaitingApproval;
|
|
1737
|
+
ctx.__resumeStage = "tools";
|
|
1738
|
+
ctx.__approvalResolved = { id: entry.id, status: entry.status };
|
|
1739
|
+
return {
|
|
1740
|
+
...state,
|
|
1741
|
+
pendingApprovals: approvals,
|
|
1742
|
+
ctx
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/utils/utilTokens.ts
|
|
1747
|
+
function countApproxTokens(text) {
|
|
1748
|
+
if (!text) return 0;
|
|
1749
|
+
return Math.ceil(text.length / 4);
|
|
1750
|
+
}
|
|
578
1751
|
|
|
579
1752
|
// src/agent.ts
|
|
580
1753
|
function createAgent(opts) {
|
|
@@ -582,7 +1755,7 @@ function createAgent(opts) {
|
|
|
582
1755
|
const agentCore = createAgentCoreNode(opts);
|
|
583
1756
|
const toolsBase = [...opts.tools ?? []];
|
|
584
1757
|
if (opts.outputSchema) {
|
|
585
|
-
const responseTool =
|
|
1758
|
+
const responseTool = createTool({
|
|
586
1759
|
name: "response",
|
|
587
1760
|
description: "Finalize the answer by returning the final structured JSON matching the required schema. Call exactly once when you are fully done, then stop.",
|
|
588
1761
|
schema: opts.outputSchema,
|
|
@@ -591,15 +1764,193 @@ function createAgent(opts) {
|
|
|
591
1764
|
toolsBase.push(responseTool);
|
|
592
1765
|
}
|
|
593
1766
|
const toolsNode = createToolsNode(toolsBase, opts);
|
|
594
|
-
const finalizeNode = createToolLimitFinalizeNode(
|
|
595
|
-
|
|
1767
|
+
const finalizeNode = createToolLimitFinalizeNode();
|
|
1768
|
+
const mergeGuardrailOutcomes = (prev, next) => {
|
|
1769
|
+
if (!prev) return next;
|
|
1770
|
+
return {
|
|
1771
|
+
ok: prev.ok && next.ok,
|
|
1772
|
+
incidents: [...prev.incidents, ...next.incidents]
|
|
1773
|
+
};
|
|
1774
|
+
};
|
|
1775
|
+
const ensureGuardrailStore = (state) => {
|
|
1776
|
+
const ctx = state.ctx = state.ctx || {};
|
|
1777
|
+
const existing = ctx.__guardrailStore || {
|
|
1778
|
+
lastRequestLength: -1,
|
|
1779
|
+
lastResponseLength: -1
|
|
1780
|
+
};
|
|
1781
|
+
ctx.__guardrailStore = existing;
|
|
1782
|
+
return existing;
|
|
1783
|
+
};
|
|
1784
|
+
const getGuardrailConfig = (state) => {
|
|
1785
|
+
const agentGuardrails = state.agent?.guardrails;
|
|
1786
|
+
return Array.isArray(agentGuardrails) ? agentGuardrails : Array.isArray(opts.guardrails) ? opts.guardrails : [];
|
|
1787
|
+
};
|
|
1788
|
+
const runtime = {
|
|
1789
|
+
name: opts.name,
|
|
1790
|
+
version: opts.version,
|
|
1791
|
+
model: opts.model,
|
|
1792
|
+
tools: toolsBase,
|
|
1793
|
+
guardrails: opts.guardrails,
|
|
1794
|
+
systemPrompt: void 0,
|
|
1795
|
+
limits: opts.limits,
|
|
1796
|
+
useTodoList: void 0,
|
|
1797
|
+
outputSchema: opts.outputSchema,
|
|
1798
|
+
tracing: opts.tracing
|
|
1799
|
+
};
|
|
1800
|
+
async function runLoop(initial, config, emit) {
|
|
596
1801
|
let state = await resolver(initial);
|
|
597
|
-
|
|
598
|
-
|
|
1802
|
+
if (state.ctx?.__paused) {
|
|
1803
|
+
const nextCtx = { ...state.ctx };
|
|
1804
|
+
delete nextCtx.__paused;
|
|
1805
|
+
state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
|
|
1806
|
+
}
|
|
1807
|
+
let resumeStage = null;
|
|
1808
|
+
if (state.ctx?.__resumeStage) {
|
|
1809
|
+
const nextCtx = { ...state.ctx };
|
|
1810
|
+
if (nextCtx.__resumeStage === "tools") {
|
|
1811
|
+
resumeStage = "tools";
|
|
1812
|
+
}
|
|
1813
|
+
delete nextCtx.__resumeStage;
|
|
1814
|
+
state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
|
|
1815
|
+
}
|
|
1816
|
+
const maxToolCalls = opts.limits?.maxToolCalls === void 0 ? 50 : opts.limits?.maxToolCalls;
|
|
1817
|
+
const iterationLimit = maxToolCalls === Infinity ? 100 : Math.max(maxToolCalls * 3 + 10, 40);
|
|
599
1818
|
let iterations = 0;
|
|
1819
|
+
const onStateChange = config?.onStateChange;
|
|
1820
|
+
const checkpointReason = config?.checkpointReason;
|
|
1821
|
+
let pausedStage = null;
|
|
1822
|
+
const checkpointIfRequested = (stage) => {
|
|
1823
|
+
if (typeof onStateChange !== "function") return false;
|
|
1824
|
+
let result = false;
|
|
1825
|
+
try {
|
|
1826
|
+
result = onStateChange(state);
|
|
1827
|
+
} catch {
|
|
1828
|
+
result = false;
|
|
1829
|
+
}
|
|
1830
|
+
if (!result) return false;
|
|
1831
|
+
const ctx = { ...state.ctx || {} };
|
|
1832
|
+
ctx.__paused = {
|
|
1833
|
+
stage,
|
|
1834
|
+
iteration: iterations,
|
|
1835
|
+
reason: checkpointReason,
|
|
1836
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1837
|
+
};
|
|
1838
|
+
state = { ...state, ctx };
|
|
1839
|
+
pausedStage = stage;
|
|
1840
|
+
return true;
|
|
1841
|
+
};
|
|
600
1842
|
while (iterations < iterationLimit) {
|
|
601
1843
|
iterations++;
|
|
602
|
-
|
|
1844
|
+
const skippingAgent = resumeStage === "tools";
|
|
1845
|
+
if (!skippingAgent) {
|
|
1846
|
+
if (checkpointIfRequested("before_guardrails")) break;
|
|
1847
|
+
const preGuardrails = getGuardrailConfig(state);
|
|
1848
|
+
if (preGuardrails.length > 0) {
|
|
1849
|
+
const store = ensureGuardrailStore(state);
|
|
1850
|
+
if (store.lastRequestLength !== state.messages.length) {
|
|
1851
|
+
const outcome = await evaluateGuardrails({
|
|
1852
|
+
guardrails: preGuardrails,
|
|
1853
|
+
phase: "request" /* Request */,
|
|
1854
|
+
state,
|
|
1855
|
+
runtime: state.agent || runtime,
|
|
1856
|
+
options: opts,
|
|
1857
|
+
emit
|
|
1858
|
+
});
|
|
1859
|
+
store.lastRequestLength = state.messages.length;
|
|
1860
|
+
state.guardrailResult = mergeGuardrailOutcomes(state.guardrailResult, outcome);
|
|
1861
|
+
const blocking = outcome.incidents.find((incident) => incident.disposition === "block");
|
|
1862
|
+
if (blocking) {
|
|
1863
|
+
const guardMessage = {
|
|
1864
|
+
role: "assistant",
|
|
1865
|
+
name: "guardrail",
|
|
1866
|
+
content: blocking.reason || "Request blocked by guardrail policy.",
|
|
1867
|
+
metadata: {
|
|
1868
|
+
guardrail: {
|
|
1869
|
+
phase: "request" /* Request */,
|
|
1870
|
+
incidents: outcome.incidents
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
state = { ...state, messages: [...state.messages, guardMessage] };
|
|
1875
|
+
const ctx = state.ctx = state.ctx || {};
|
|
1876
|
+
ctx.__guardrailBlocked = {
|
|
1877
|
+
phase: "request" /* Request */,
|
|
1878
|
+
incident: blocking
|
|
1879
|
+
};
|
|
1880
|
+
break;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
let maxTok = 5e4;
|
|
1885
|
+
if (opts.summarization && typeof opts.summarization === "object" && opts.summarization.maxTokens) {
|
|
1886
|
+
maxTok = opts.summarization.maxTokens;
|
|
1887
|
+
}
|
|
1888
|
+
if (maxTok) {
|
|
1889
|
+
const allText = (state.messages || []).map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("") : "").join("\n");
|
|
1890
|
+
const tokenCount = countApproxTokens(allText);
|
|
1891
|
+
if (tokenCount > maxTok) {
|
|
1892
|
+
const ctx = { ...state.ctx || {}, __needsSummarization: true };
|
|
1893
|
+
state = { ...state, ctx };
|
|
1894
|
+
break;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
state = { ...state, ...await agentCore(state) };
|
|
1898
|
+
if (checkpointIfRequested("after_agent")) break;
|
|
1899
|
+
const postGuardrails = getGuardrailConfig(state);
|
|
1900
|
+
if (postGuardrails.length > 0) {
|
|
1901
|
+
const store = ensureGuardrailStore(state);
|
|
1902
|
+
if (store.lastResponseLength !== state.messages.length) {
|
|
1903
|
+
const outcome = await evaluateGuardrails({
|
|
1904
|
+
guardrails: postGuardrails,
|
|
1905
|
+
phase: "response" /* Response */,
|
|
1906
|
+
state,
|
|
1907
|
+
runtime: state.agent || runtime,
|
|
1908
|
+
options: opts,
|
|
1909
|
+
emit
|
|
1910
|
+
});
|
|
1911
|
+
store.lastResponseLength = state.messages.length;
|
|
1912
|
+
state.guardrailResult = mergeGuardrailOutcomes(state.guardrailResult, outcome);
|
|
1913
|
+
const blocking = outcome.incidents.find((incident) => incident.disposition === "block");
|
|
1914
|
+
if (blocking) {
|
|
1915
|
+
const updatedMessages = [...state.messages];
|
|
1916
|
+
const replaced = updatedMessages.pop();
|
|
1917
|
+
updatedMessages.push({
|
|
1918
|
+
role: "assistant",
|
|
1919
|
+
name: "guardrail",
|
|
1920
|
+
content: blocking.reason || "Response blocked by guardrail policy.",
|
|
1921
|
+
metadata: {
|
|
1922
|
+
guardrail: {
|
|
1923
|
+
phase: "response" /* Response */,
|
|
1924
|
+
incidents: outcome.incidents,
|
|
1925
|
+
replaced
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
state = { ...state, messages: updatedMessages };
|
|
1930
|
+
const ctx = state.ctx = state.ctx || {};
|
|
1931
|
+
ctx.__guardrailBlocked = {
|
|
1932
|
+
phase: "response" /* Response */,
|
|
1933
|
+
incident: blocking,
|
|
1934
|
+
replaced
|
|
1935
|
+
};
|
|
1936
|
+
break;
|
|
1937
|
+
} else if (outcome.incidents.length > 0) {
|
|
1938
|
+
const last = state.messages[state.messages.length - 1];
|
|
1939
|
+
if (last) {
|
|
1940
|
+
last.metadata = {
|
|
1941
|
+
...last.metadata || {},
|
|
1942
|
+
guardrail: {
|
|
1943
|
+
phase: "response" /* Response */,
|
|
1944
|
+
incidents: outcome.incidents
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
} else {
|
|
1952
|
+
resumeStage = null;
|
|
1953
|
+
}
|
|
603
1954
|
const lastMsg = state.messages[state.messages.length - 1];
|
|
604
1955
|
const toolCalls = Array.isArray(lastMsg?.tool_calls) ? lastMsg.tool_calls : [];
|
|
605
1956
|
const toolCallCount = state.toolCallCount || 0;
|
|
@@ -612,79 +1963,128 @@ function createAgent(opts) {
|
|
|
612
1963
|
}
|
|
613
1964
|
if (toolCalls.length === 0) break;
|
|
614
1965
|
state = { ...state, ...await toolsNode(state) };
|
|
1966
|
+
if (state.ctx?.__awaitingApproval) break;
|
|
1967
|
+
if (checkpointIfRequested("after_tools")) break;
|
|
615
1968
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
616
1969
|
}
|
|
1970
|
+
if (opts.outputSchema && !state.ctx?.__finalizedDueToStructuredOutput) {
|
|
1971
|
+
const last = state.messages[state.messages.length - 1];
|
|
1972
|
+
const lastHasToolCalls = Array.isArray(last?.tool_calls) && last.tool_calls.length > 0;
|
|
1973
|
+
if (!lastHasToolCalls) {
|
|
1974
|
+
const forceMsg = {
|
|
1975
|
+
role: "system",
|
|
1976
|
+
content: [
|
|
1977
|
+
"A structured output schema is active.",
|
|
1978
|
+
"You MUST now call tool `response` with the final JSON object that matches the schema.",
|
|
1979
|
+
"Do not write the JSON in the assistant message.",
|
|
1980
|
+
"Call `response` exactly once, then stop."
|
|
1981
|
+
].join("\n")
|
|
1982
|
+
};
|
|
1983
|
+
const alreadyForced = Boolean(state.ctx?.__structuredOutputForceFinalize);
|
|
1984
|
+
if (!alreadyForced) {
|
|
1985
|
+
const ctx = { ...state.ctx || {}, __structuredOutputForceFinalize: true };
|
|
1986
|
+
state = { ...state, messages: [...state.messages, forceMsg], ctx };
|
|
1987
|
+
try {
|
|
1988
|
+
state = { ...state, ...await agentCore(state) };
|
|
1989
|
+
const lastAfter = state.messages[state.messages.length - 1];
|
|
1990
|
+
const toolCallsAfter = Array.isArray(lastAfter?.tool_calls) ? lastAfter.tool_calls : [];
|
|
1991
|
+
if (toolCallsAfter.length > 0) {
|
|
1992
|
+
state = { ...state, ...await toolsNode(state) };
|
|
1993
|
+
}
|
|
1994
|
+
} catch {
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
if (!pausedStage && typeof onStateChange === "function" && onStateChange(state)) {
|
|
2000
|
+
checkpointIfRequested("after_loop");
|
|
2001
|
+
}
|
|
617
2002
|
return state;
|
|
618
2003
|
}
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
2004
|
+
const invokeAgent = async (input, config) => {
|
|
2005
|
+
const onEvent = config?.onEvent;
|
|
2006
|
+
const emit = (e) => {
|
|
2007
|
+
try {
|
|
2008
|
+
onEvent?.(e);
|
|
2009
|
+
} catch {
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
const traceSession = createTraceSession(opts);
|
|
2013
|
+
const ctx = { ...input.ctx || {}, __onEvent: onEvent };
|
|
2014
|
+
if (traceSession) ctx.__traceSession = traceSession;
|
|
2015
|
+
const initial = {
|
|
2016
|
+
messages: input.messages || [],
|
|
2017
|
+
toolCallCount: input.toolCallCount || 0,
|
|
2018
|
+
toolCache: input.toolCache || {},
|
|
2019
|
+
toolHistory: input.toolHistory || [],
|
|
2020
|
+
metadata: input.metadata,
|
|
2021
|
+
ctx,
|
|
2022
|
+
pendingApprovals: input.pendingApprovals || [],
|
|
2023
|
+
agent: input.agent || runtime,
|
|
2024
|
+
usage: input.usage || { perRequest: [], totals: {} }
|
|
2025
|
+
};
|
|
2026
|
+
let res;
|
|
2027
|
+
try {
|
|
2028
|
+
res = await runLoop(initial, config, emit);
|
|
2029
|
+
} catch (err) {
|
|
2030
|
+
await finalizeTraceSession(traceSession, {
|
|
2031
|
+
agentRuntime: runtime,
|
|
2032
|
+
status: "error",
|
|
2033
|
+
error: { message: err?.message, stack: err?.stack }
|
|
2034
|
+
});
|
|
2035
|
+
throw err;
|
|
2036
|
+
}
|
|
2037
|
+
await finalizeTraceSession(traceSession, {
|
|
2038
|
+
agentRuntime: res.agent || runtime,
|
|
2039
|
+
status: "success"
|
|
2040
|
+
});
|
|
2041
|
+
const finalMsg = res.messages[res.messages.length - 1];
|
|
2042
|
+
let content = "";
|
|
2043
|
+
if (typeof finalMsg?.content === "string") content = finalMsg.content;
|
|
2044
|
+
else if (Array.isArray(finalMsg?.content)) content = finalMsg.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
|
|
2045
|
+
let parsed = void 0;
|
|
2046
|
+
const schema = opts.outputSchema;
|
|
2047
|
+
if (schema && res.ctx?.__structuredOutputParsed) {
|
|
2048
|
+
parsed = res.ctx.__structuredOutputParsed;
|
|
2049
|
+
} else if (schema && content) {
|
|
2050
|
+
let jsonText = null;
|
|
2051
|
+
const fenced = content.match(/```(?:json)?\n([\s\S]*?)```/i);
|
|
2052
|
+
if (fenced && fenced[1]) jsonText = fenced[1].trim();
|
|
2053
|
+
else {
|
|
2054
|
+
const braceIdx = content.indexOf("{");
|
|
2055
|
+
const bracketIdx = content.indexOf("[");
|
|
2056
|
+
const start = [braceIdx, bracketIdx].filter((i) => i >= 0).sort((a, b) => a - b)[0];
|
|
2057
|
+
if (start !== void 0) jsonText = content.slice(start).trim();
|
|
2058
|
+
}
|
|
2059
|
+
try {
|
|
2060
|
+
const raw = JSON.parse(jsonText ?? content);
|
|
2061
|
+
parsed = schema.parse(raw);
|
|
2062
|
+
} catch {
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
emit({ type: "finalAnswer", content: typeof finalMsg?.content === "string" ? finalMsg.content : content });
|
|
2066
|
+
return {
|
|
2067
|
+
content,
|
|
2068
|
+
output: parsed,
|
|
2069
|
+
metadata: { usage: res.usage },
|
|
2070
|
+
messages: res.messages,
|
|
2071
|
+
state: res
|
|
2072
|
+
};
|
|
2073
|
+
};
|
|
2074
|
+
const snapshotState = (state, options) => captureSnapshot(state, options);
|
|
2075
|
+
const resumeAgent = async (snapshot, config, restoreOptions) => {
|
|
2076
|
+
const restoredState = restoreSnapshot(snapshot, restoreOptions);
|
|
2077
|
+
return invokeAgent(restoredState, config);
|
|
627
2078
|
};
|
|
2079
|
+
const resolveToolApproval = (state, resolution) => resolveToolApprovalState(state, resolution);
|
|
628
2080
|
const instance = {
|
|
629
|
-
invoke:
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
onEvent?.(e);
|
|
634
|
-
} catch {
|
|
635
|
-
}
|
|
636
|
-
};
|
|
637
|
-
const initial = {
|
|
638
|
-
messages: input.messages || [],
|
|
639
|
-
summaries: input.summaries || [],
|
|
640
|
-
toolCallCount: input.toolCallCount || 0,
|
|
641
|
-
toolCache: input.toolCache || {},
|
|
642
|
-
toolHistory: input.toolHistory || [],
|
|
643
|
-
toolHistoryArchived: input.toolHistoryArchived || [],
|
|
644
|
-
metadata: input.metadata,
|
|
645
|
-
ctx: { ...input.ctx || {}, __onEvent: onEvent },
|
|
646
|
-
plan: input.plan || null,
|
|
647
|
-
planVersion: input.planVersion || 0,
|
|
648
|
-
agent: input.agent || runtime,
|
|
649
|
-
usage: input.usage || { perRequest: [], totals: {} }
|
|
650
|
-
};
|
|
651
|
-
const res = await runLoop(initial, config);
|
|
652
|
-
const finalMsg = res.messages[res.messages.length - 1];
|
|
653
|
-
let content = "";
|
|
654
|
-
if (typeof finalMsg?.content === "string") content = finalMsg.content;
|
|
655
|
-
else if (Array.isArray(finalMsg?.content)) content = finalMsg.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
|
|
656
|
-
let parsed = void 0;
|
|
657
|
-
const schema = opts.outputSchema;
|
|
658
|
-
if (schema && res.ctx?.__structuredOutputParsed) {
|
|
659
|
-
parsed = res.ctx.__structuredOutputParsed;
|
|
660
|
-
} else if (schema && content) {
|
|
661
|
-
let jsonText = null;
|
|
662
|
-
const fenced = content.match(/```(?:json)?\n([\s\S]*?)```/i);
|
|
663
|
-
if (fenced && fenced[1]) jsonText = fenced[1].trim();
|
|
664
|
-
else {
|
|
665
|
-
const braceIdx = content.indexOf("{");
|
|
666
|
-
const bracketIdx = content.indexOf("[");
|
|
667
|
-
const start = [braceIdx, bracketIdx].filter((i) => i >= 0).sort((a, b) => a - b)[0];
|
|
668
|
-
if (start !== void 0) jsonText = content.slice(start).trim();
|
|
669
|
-
}
|
|
670
|
-
try {
|
|
671
|
-
const raw = JSON.parse(jsonText ?? content);
|
|
672
|
-
parsed = schema.parse(raw);
|
|
673
|
-
} catch {
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
emit({ type: "finalAnswer", content: typeof finalMsg?.content === "string" ? finalMsg.content : content });
|
|
677
|
-
return {
|
|
678
|
-
content,
|
|
679
|
-
output: parsed,
|
|
680
|
-
metadata: { usage: res.usage },
|
|
681
|
-
messages: res.messages,
|
|
682
|
-
state: res
|
|
683
|
-
};
|
|
684
|
-
},
|
|
2081
|
+
invoke: invokeAgent,
|
|
2082
|
+
snapshot: snapshotState,
|
|
2083
|
+
resume: resumeAgent,
|
|
2084
|
+
resolveToolApproval,
|
|
685
2085
|
asTool: ({ toolName, description, inputDescription }) => {
|
|
686
|
-
const schema =
|
|
687
|
-
return
|
|
2086
|
+
const schema = zod.z.object({ input: zod.z.string().describe(inputDescription || "Input for delegated agent") });
|
|
2087
|
+
return createTool({
|
|
688
2088
|
name: toolName,
|
|
689
2089
|
description: description || `Delegate task to agent ${opts.name || "Agent"}`,
|
|
690
2090
|
schema,
|
|
@@ -696,8 +2096,8 @@ function createAgent(opts) {
|
|
|
696
2096
|
},
|
|
697
2097
|
asHandoff: ({ toolName, description, schema }) => {
|
|
698
2098
|
const finalName = toolName || `handoff_to_${runtime.name || "agent"}`;
|
|
699
|
-
const zschema = schema ||
|
|
700
|
-
|
|
2099
|
+
const zschema = schema || zod.z.object({ reason: zod.z.string().describe("Reason for handoff") });
|
|
2100
|
+
createTool({
|
|
701
2101
|
name: finalName,
|
|
702
2102
|
description: description || `Handoff control to agent ${runtime.name || "Agent"}`,
|
|
703
2103
|
schema: zschema,
|
|
@@ -709,8 +2109,8 @@ function createAgent(opts) {
|
|
|
709
2109
|
};
|
|
710
2110
|
if (opts.handoffs && Array.isArray(opts.handoffs)) {
|
|
711
2111
|
const handoffTools = opts.handoffs.map((h) => {
|
|
712
|
-
const schema = h.schema ||
|
|
713
|
-
return
|
|
2112
|
+
const schema = h.schema || zod.z.object({ reason: zod.z.string().describe("Reason for handoff") });
|
|
2113
|
+
return createTool({
|
|
714
2114
|
name: h.toolName,
|
|
715
2115
|
description: h.description || `Handoff to ${h.target.__runtime.name || "agent"}`,
|
|
716
2116
|
schema,
|
|
@@ -727,6 +2127,7 @@ function createAgentNode(opts) {
|
|
|
727
2127
|
return async (state) => {
|
|
728
2128
|
const runtime = state.agent || {
|
|
729
2129
|
name: opts.name,
|
|
2130
|
+
version: opts.version,
|
|
730
2131
|
model: opts.model,
|
|
731
2132
|
tools: opts.tools || [],
|
|
732
2133
|
systemPrompt: opts.systemPrompt,
|
|
@@ -735,12 +2136,6 @@ function createAgentNode(opts) {
|
|
|
735
2136
|
outputSchema: opts.outputSchema
|
|
736
2137
|
};
|
|
737
2138
|
const tools = runtime.tools ?? [];
|
|
738
|
-
const limits = {
|
|
739
|
-
maxToolCalls: runtime.limits?.maxToolCalls ?? 10,
|
|
740
|
-
toolOutputTokenLimit: runtime.limits?.toolOutputTokenLimit ?? 5e3,
|
|
741
|
-
contextTokenLimit: runtime.limits?.contextTokenLimit ?? 6e4,
|
|
742
|
-
summaryTokenLimit: runtime.limits?.summaryTokenLimit ?? 5e4
|
|
743
|
-
};
|
|
744
2139
|
const modelWithTools = runtime.model?.bindTools ? runtime.model.bindTools(tools) : runtime.model;
|
|
745
2140
|
const structuredOutputHint = runtime.outputSchema ? [
|
|
746
2141
|
"A structured output schema is active.",
|
|
@@ -757,19 +2152,60 @@ function createAgentNode(opts) {
|
|
|
757
2152
|
)
|
|
758
2153
|
};
|
|
759
2154
|
const messages = [systemMsg, ...state.messages];
|
|
760
|
-
const
|
|
761
|
-
const
|
|
2155
|
+
const traceSession = state.ctx?.__traceSession;
|
|
2156
|
+
const actorName = runtime.name ?? opts.name ?? "agent";
|
|
2157
|
+
const actorVersion = runtime.version ?? opts.version;
|
|
2158
|
+
const start = Date.now();
|
|
2159
|
+
const promptMessages = [systemMsg, ...state.messages];
|
|
2160
|
+
const shouldLogPrompt = !!traceSession && traceSession.resolvedConfig.logData;
|
|
2161
|
+
const promptPayload = shouldLogPrompt ? sanitizeTracePayload(promptMessages) : void 0;
|
|
2162
|
+
const promptBytes = promptPayload !== void 0 ? estimatePayloadBytes(promptPayload) : void 0;
|
|
2163
|
+
let response;
|
|
2164
|
+
try {
|
|
2165
|
+
response = await modelWithTools.invoke(promptMessages);
|
|
2166
|
+
} catch (err) {
|
|
2167
|
+
const durationMs2 = Date.now() - start;
|
|
2168
|
+
recordTraceEvent(traceSession, {
|
|
2169
|
+
type: "ai_call",
|
|
2170
|
+
label: "Assistant Error",
|
|
2171
|
+
actor: { scope: "agent", name: actorName, role: "assistant", version: actorVersion },
|
|
2172
|
+
status: "error",
|
|
2173
|
+
durationMs: durationMs2,
|
|
2174
|
+
requestBytes: promptBytes,
|
|
2175
|
+
model: getModelName(runtime.model || opts.model),
|
|
2176
|
+
provider: getProviderName(runtime.model || opts.model),
|
|
2177
|
+
error: { message: err?.message || String(err), stack: err?.stack },
|
|
2178
|
+
messageList: promptMessages
|
|
2179
|
+
});
|
|
2180
|
+
throw err;
|
|
2181
|
+
}
|
|
762
2182
|
const messagesWithResponse = [
|
|
763
2183
|
...messages,
|
|
764
2184
|
response
|
|
765
2185
|
];
|
|
766
|
-
const messagesWithSystem = [
|
|
767
|
-
systemMsg,
|
|
768
|
-
...messagesWithResponse
|
|
769
|
-
];
|
|
770
2186
|
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.usage;
|
|
771
2187
|
const normalized = normalizeUsage(rawUsage);
|
|
772
2188
|
const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
|
|
2189
|
+
const providerName = getProviderName(runtime.model || opts.model);
|
|
2190
|
+
const durationMs = Date.now() - start;
|
|
2191
|
+
const shouldLogResponse = !!traceSession && traceSession.resolvedConfig.logData;
|
|
2192
|
+
const responsePayload = shouldLogResponse ? sanitizeTracePayload(response) : void 0;
|
|
2193
|
+
const responseBytes = responsePayload !== void 0 ? estimatePayloadBytes(responsePayload) : void 0;
|
|
2194
|
+
recordTraceEvent(traceSession, {
|
|
2195
|
+
type: "ai_call",
|
|
2196
|
+
label: "Assistant Response",
|
|
2197
|
+
actor: { scope: "agent", name: actorName, role: "assistant", version: actorVersion },
|
|
2198
|
+
durationMs,
|
|
2199
|
+
inputTokens: normalized?.prompt_tokens,
|
|
2200
|
+
outputTokens: normalized?.completion_tokens,
|
|
2201
|
+
totalTokens: normalized?.total_tokens,
|
|
2202
|
+
cachedInputTokens: normalized?.prompt_tokens_details?.cached_tokens,
|
|
2203
|
+
requestBytes: promptBytes,
|
|
2204
|
+
responseBytes,
|
|
2205
|
+
model: modelName,
|
|
2206
|
+
provider: providerName,
|
|
2207
|
+
messageList: messagesWithResponse
|
|
2208
|
+
});
|
|
773
2209
|
if (normalized) {
|
|
774
2210
|
const usageState = state.usage || { perRequest: [], totals: {} };
|
|
775
2211
|
const id = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -790,262 +2226,100 @@ function createAgentNode(opts) {
|
|
|
790
2226
|
};
|
|
791
2227
|
state.usage = usageState;
|
|
792
2228
|
}
|
|
793
|
-
if (debugSession) {
|
|
794
|
-
const idx = bumpStep(debugSession);
|
|
795
|
-
const fileName = `${String(idx).padStart(2, "0")}.md`;
|
|
796
|
-
const markdown = formatMarkdown({
|
|
797
|
-
modelName: getModelName(runtime.model || opts.model),
|
|
798
|
-
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
799
|
-
limits: runtime.limits,
|
|
800
|
-
// IMPORTANT: log only per-request normalized usage
|
|
801
|
-
usage: normalized,
|
|
802
|
-
tools: serializeAgentTools(tools),
|
|
803
|
-
messages: messagesWithSystem
|
|
804
|
-
});
|
|
805
|
-
await writeStepMarkdown(debugSession, fileName, markdown, {
|
|
806
|
-
messages: messagesWithSystem,
|
|
807
|
-
usage: normalized,
|
|
808
|
-
modelName: getModelName(runtime.model || opts.model),
|
|
809
|
-
limits: runtime.limits,
|
|
810
|
-
tools: serializeAgentTools(tools)
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
2229
|
return { messages: messagesWithResponse, usage: state.usage };
|
|
814
2230
|
};
|
|
815
2231
|
}
|
|
816
2232
|
|
|
817
|
-
// src/nodes/contextSummarize.ts
|
|
818
|
-
var import_nanoid2 = require("nanoid");
|
|
819
|
-
|
|
820
|
-
// src/utils/utilTokens.ts
|
|
821
|
-
function countApproxTokens(text) {
|
|
822
|
-
if (!text) return 0;
|
|
823
|
-
return Math.ceil(text.length / 4);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
2233
|
// src/nodes/contextSummarize.ts
|
|
827
2234
|
var systemMessage = (content) => ({ role: "system", content });
|
|
828
2235
|
var humanMessage = (content) => ({ role: "user", content });
|
|
829
|
-
|
|
2236
|
+
function getSummarizationConfig(opts) {
|
|
2237
|
+
if (typeof opts.summarization === "object") {
|
|
2238
|
+
return {
|
|
2239
|
+
enabled: opts.summarization.enable !== false,
|
|
2240
|
+
maxTokens: opts.summarization.maxTokens
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
return {
|
|
2244
|
+
enabled: opts.summarization !== false,
|
|
2245
|
+
maxTokens: 5e4
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
830
2248
|
function createContextSummarizeNode(opts) {
|
|
831
2249
|
return async (state) => {
|
|
832
|
-
const
|
|
833
|
-
|
|
2250
|
+
const config = getSummarizationConfig(opts);
|
|
2251
|
+
if (!config.enabled) return {};
|
|
834
2252
|
const model = opts.model;
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (
|
|
840
|
-
|
|
841
|
-
for (const item of live) {
|
|
842
|
-
if (item.tool_call_id) byCallId.set(item.tool_call_id, item);
|
|
843
|
-
}
|
|
844
|
-
for (const item of live) {
|
|
845
|
-
if (!item.executionId) item.executionId = (0, import_nanoid2.nanoid)();
|
|
846
|
-
}
|
|
847
|
-
const getText = (m) => {
|
|
848
|
-
if (!m) return "";
|
|
849
|
-
const c = m.content;
|
|
850
|
-
if (typeof c === "string") return c;
|
|
851
|
-
if (Array.isArray(c)) return c.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("");
|
|
852
|
-
return String(c ?? "");
|
|
853
|
-
};
|
|
854
|
-
const sysText = "You are a concise summarization assistant for a tool-using agent. Summarize prior tool executions into a compact brief that preserves key facts, decisions, and outputs. Reference executionId values where helpful. Avoid repeating raw data; prefer synthesis.";
|
|
855
|
-
const sys = systemMessage(sysText);
|
|
856
|
-
let summaryText = "";
|
|
857
|
-
try {
|
|
858
|
-
const limit = Math.max(1e3, Number(summaryTokenLimit) || 1e5);
|
|
859
|
-
const safetyBuffer = Math.floor(Math.min(4e3, limit * 0.05));
|
|
860
|
-
const perCallBudget = Math.max(500, limit - safetyBuffer);
|
|
861
|
-
const safeToolPair = opts?.safeToolPair !== false;
|
|
862
|
-
const chunks = [];
|
|
863
|
-
let current = [];
|
|
864
|
-
let currentTokens = countApproxTokens(sysText);
|
|
865
|
-
let pendingToolCalls = /* @__PURE__ */ new Set();
|
|
866
|
-
const extractToolCallIds = (msg) => {
|
|
867
|
-
const tc = msg?.additional_kwargs?.tool_calls;
|
|
868
|
-
if (!Array.isArray(tc) || tc.length === 0) return [];
|
|
869
|
-
return tc.map((c) => c?.id).filter((id) => typeof id === "string");
|
|
870
|
-
};
|
|
871
|
-
for (let i = 0; i < messages.length; i++) {
|
|
872
|
-
const m = messages[i];
|
|
873
|
-
const text = getText(m);
|
|
874
|
-
const tks = countApproxTokens(text);
|
|
875
|
-
const isAIWithToolCalls = safeToolPair && m?.role === "assistant" && extractToolCallIds(m).length > 0;
|
|
876
|
-
const isToolMsg = safeToolPair && m?.role === "tool";
|
|
877
|
-
const toolCallId = isToolMsg ? m.tool_call_id : void 0;
|
|
878
|
-
const wouldExceed = currentTokens + tks > perCallBudget && current.length > 0;
|
|
879
|
-
if (wouldExceed) {
|
|
880
|
-
let forceAddToCurrent = false;
|
|
881
|
-
if (safeToolPair) {
|
|
882
|
-
if (isToolMsg && toolCallId && pendingToolCalls.has(toolCallId)) {
|
|
883
|
-
forceAddToCurrent = true;
|
|
884
|
-
} else if (isAIWithToolCalls) {
|
|
885
|
-
} else if (pendingToolCalls.size > 0) {
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
if (!forceAddToCurrent) {
|
|
889
|
-
if (current.length > 0) {
|
|
890
|
-
chunks.push(current);
|
|
891
|
-
}
|
|
892
|
-
current = [];
|
|
893
|
-
currentTokens = countApproxTokens(sysText);
|
|
894
|
-
pendingToolCalls = /* @__PURE__ */ new Set();
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
current.push(m);
|
|
898
|
-
currentTokens += tks;
|
|
899
|
-
if (isAIWithToolCalls) {
|
|
900
|
-
for (const id of extractToolCallIds(m)) pendingToolCalls.add(id);
|
|
901
|
-
}
|
|
902
|
-
if (isToolMsg && toolCallId && pendingToolCalls.has(toolCallId)) {
|
|
903
|
-
pendingToolCalls.delete(toolCallId);
|
|
904
|
-
}
|
|
905
|
-
if (currentTokens >= perCallBudget && pendingToolCalls.size === 0) {
|
|
906
|
-
chunks.push(current);
|
|
907
|
-
current = [];
|
|
908
|
-
currentTokens = countApproxTokens(sysText);
|
|
909
|
-
pendingToolCalls = /* @__PURE__ */ new Set();
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
if (current.length > 0) {
|
|
913
|
-
chunks.push(current);
|
|
914
|
-
}
|
|
915
|
-
const partials = [];
|
|
916
|
-
for (const chunk of chunks) {
|
|
917
|
-
let resp = null;
|
|
918
|
-
try {
|
|
919
|
-
resp = await (model?.invoke ? model.invoke([sys, ...chunk]) : null);
|
|
920
|
-
const c = resp?.content;
|
|
921
|
-
const text = typeof c === "string" ? c : Array.isArray(c) ? c.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("") : "";
|
|
922
|
-
partials.push((text || "").trim());
|
|
923
|
-
} catch (e) {
|
|
924
|
-
console.error("Summarization chunk failed:", e);
|
|
925
|
-
partials.push("[summary unavailable for one chunk]");
|
|
926
|
-
}
|
|
2253
|
+
const messages = state.messages || [];
|
|
2254
|
+
const conversationText = messages.map((m) => {
|
|
2255
|
+
let content = "";
|
|
2256
|
+
if (typeof m.content === "string") content = m.content;
|
|
2257
|
+
else if (Array.isArray(m.content)) {
|
|
2258
|
+
content = m.content.map((c) => c.text || JSON.stringify(c)).join(" ");
|
|
927
2259
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
group = [];
|
|
942
|
-
toks = countApproxTokens(mergeSysText);
|
|
943
|
-
}
|
|
944
|
-
group.push(tt);
|
|
945
|
-
toks += tk;
|
|
946
|
-
}
|
|
947
|
-
if (group.length > 0) groups.push(group);
|
|
948
|
-
const next = [];
|
|
949
|
-
for (const g of groups) {
|
|
950
|
-
const mergePrompt = humanMessage(`Merge the following partial summaries into a single concise brief. Avoid duplication, keep key facts and decisions.
|
|
2260
|
+
let prefix = `${m.role.toUpperCase()}`;
|
|
2261
|
+
if (m.name) prefix += ` (${m.name})`;
|
|
2262
|
+
return `${prefix}: ${content}`;
|
|
2263
|
+
}).join("\n\n");
|
|
2264
|
+
const summaryPrompt = [
|
|
2265
|
+
systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
|
|
2266
|
+
humanMessage(
|
|
2267
|
+
`Please summarize the following conversation history.
|
|
2268
|
+
Focus on:
|
|
2269
|
+
- User goals and intent.
|
|
2270
|
+
- Key decisions made and actions taken.
|
|
2271
|
+
- Important tool outputs and data retrieved.
|
|
2272
|
+
- Current state of the task.
|
|
951
2273
|
|
|
952
|
-
|
|
2274
|
+
Conversation:
|
|
2275
|
+
${conversationText}
|
|
953
2276
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
}
|
|
965
|
-
return mergeIteratively(next);
|
|
966
|
-
};
|
|
967
|
-
summaryText = await mergeIteratively(partials);
|
|
968
|
-
if (!summaryText) summaryText = "Context summarized.";
|
|
969
|
-
} catch (e) {
|
|
970
|
-
console.error("Error during context summarization:", e);
|
|
971
|
-
summaryText = "Context summarized.";
|
|
2277
|
+
Summary:`
|
|
2278
|
+
)
|
|
2279
|
+
];
|
|
2280
|
+
let summaryText = "Summary unavailable.";
|
|
2281
|
+
try {
|
|
2282
|
+
const response = await model.invoke(summaryPrompt);
|
|
2283
|
+
summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
|
|
2284
|
+
} catch (err) {
|
|
2285
|
+
console.error("[ContextSummarize] Failed to generate summary:", err);
|
|
2286
|
+
return {};
|
|
972
2287
|
}
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
const toolMessageObj = m;
|
|
981
|
-
if (alreadySummarized(toolMessageObj.content) || toolMessageObj.name === "context_summarize" || toolMessageObj.name === "manage_todo_list") {
|
|
982
|
-
rewritten.push(m);
|
|
983
|
-
continue;
|
|
984
|
-
}
|
|
985
|
-
const callId = toolMessageObj.tool_call_id;
|
|
986
|
-
const safeId = callId || `summarized_${(0, import_nanoid2.nanoid)(6)}`;
|
|
987
|
-
let hist = callId ? byCallId.get(callId) : void 0;
|
|
988
|
-
if (!hist) {
|
|
989
|
-
hist = {
|
|
990
|
-
executionId: (0, import_nanoid2.nanoid)(),
|
|
991
|
-
toolName: toolMessageObj.name || "unknown_tool",
|
|
992
|
-
args: null,
|
|
993
|
-
output: typeof toolMessageObj.content === "string" ? toolMessageObj.content : Array.isArray(toolMessageObj.content) ? toolMessageObj.content.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("") : String(toolMessageObj.content ?? ""),
|
|
994
|
-
rawOutput: void 0,
|
|
995
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
996
|
-
summarized: true,
|
|
997
|
-
messageId: void 0,
|
|
998
|
-
tool_call_id: callId,
|
|
999
|
-
fromCache: false
|
|
1000
|
-
};
|
|
1001
|
-
if (callId) byCallId.set(callId, hist);
|
|
1002
|
-
archived.push({ ...hist });
|
|
1003
|
-
}
|
|
1004
|
-
if (!hist.executionId) hist.executionId = (0, import_nanoid2.nanoid)();
|
|
1005
|
-
hist.summarized = true;
|
|
1006
|
-
rewritten.push({
|
|
1007
|
-
role: "tool",
|
|
1008
|
-
content: `SUMMARIZED executionId:'${hist.executionId}'`,
|
|
1009
|
-
tool_call_id: safeId,
|
|
1010
|
-
name: toolMessage.name
|
|
1011
|
-
});
|
|
1012
|
-
continue;
|
|
2288
|
+
const newMessages = messages.map((m) => {
|
|
2289
|
+
if (m.role === "tool") {
|
|
2290
|
+
if (m.content === "SUMMARIZED") return m;
|
|
2291
|
+
return {
|
|
2292
|
+
...m,
|
|
2293
|
+
content: "SUMMARIZED"
|
|
2294
|
+
};
|
|
1013
2295
|
}
|
|
1014
|
-
|
|
1015
|
-
}
|
|
1016
|
-
const
|
|
1017
|
-
const
|
|
1018
|
-
const syntheticAssistant = {
|
|
2296
|
+
return m;
|
|
2297
|
+
});
|
|
2298
|
+
const summaryToolCallId = `call_summary_${Date.now()}`;
|
|
2299
|
+
const assistantSummaryCall = {
|
|
1019
2300
|
role: "assistant",
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
2301
|
+
// "assistant"
|
|
2302
|
+
content: "Context limit reached. Summarizing conversation history to reduce token usage.",
|
|
2303
|
+
tool_calls: [
|
|
2304
|
+
{
|
|
2305
|
+
id: summaryToolCallId,
|
|
2306
|
+
type: "function",
|
|
2307
|
+
function: {
|
|
2308
|
+
name: "summarize_context",
|
|
2309
|
+
arguments: "{}"
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
]
|
|
1026
2313
|
};
|
|
1027
|
-
const
|
|
1028
|
-
const syntheticToolResp = {
|
|
2314
|
+
const toolSummaryResponse = {
|
|
1029
2315
|
role: "tool",
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
name
|
|
2316
|
+
tool_call_id: summaryToolCallId,
|
|
2317
|
+
name: "summarize_context",
|
|
2318
|
+
// Matches the tool name in the call
|
|
2319
|
+
content: summaryText
|
|
1033
2320
|
};
|
|
1034
|
-
for (const item of live) item.summarized = true;
|
|
1035
|
-
const merged = [...archived, ...live];
|
|
1036
|
-
const dedupMap = /* @__PURE__ */ new Map();
|
|
1037
|
-
for (const it of merged) {
|
|
1038
|
-
const key = it.tool_call_id || `${it.toolName}:${it.timestamp}`;
|
|
1039
|
-
dedupMap.set(key, { ...it });
|
|
1040
|
-
}
|
|
1041
|
-
const newArchived = Array.from(dedupMap.values());
|
|
1042
|
-
const onEvent = state.ctx?.__onEvent;
|
|
1043
|
-
onEvent?.({ type: "summarization", summary: summaryText, archivedCount: newArchived.length });
|
|
1044
2321
|
return {
|
|
1045
|
-
messages: [...
|
|
1046
|
-
toolHistoryArchived: newArchived,
|
|
1047
|
-
toolHistory: [],
|
|
1048
|
-
ctx: { ...state.ctx || {}, __contextSummarized: true }
|
|
2322
|
+
messages: [...newMessages, assistantSummaryCall, toolSummaryResponse]
|
|
1049
2323
|
};
|
|
1050
2324
|
};
|
|
1051
2325
|
}
|
|
@@ -1053,7 +2327,6 @@ Merged summary:`);
|
|
|
1053
2327
|
// src/utils/tokenManager.ts
|
|
1054
2328
|
async function applyTokenLimits({
|
|
1055
2329
|
state,
|
|
1056
|
-
model,
|
|
1057
2330
|
limits
|
|
1058
2331
|
}) {
|
|
1059
2332
|
const messages = state.messages || [];
|
|
@@ -1063,15 +2336,23 @@ async function applyTokenLimits({
|
|
|
1063
2336
|
const newMessages = [...messages];
|
|
1064
2337
|
return { ...state, messages: newMessages };
|
|
1065
2338
|
}
|
|
1066
|
-
|
|
1067
|
-
// src/contextTools.ts
|
|
1068
|
-
var import_tools3 = require("@langchain/core/tools");
|
|
1069
|
-
var import_zod2 = require("zod");
|
|
1070
2339
|
function createContextTools(stateRef, opts) {
|
|
1071
2340
|
const tools = [];
|
|
1072
2341
|
if (opts?.planningEnabled) {
|
|
1073
|
-
const manageTodo = (
|
|
1074
|
-
|
|
2342
|
+
const manageTodo = createTool({
|
|
2343
|
+
name: "manage_todo_list",
|
|
2344
|
+
description: "Manage a structured todo list to track progress and plan tasks throughout your coding session. Use this tool VERY frequently to ensure task visibility and proper planning.\n\nWhen to use this tool:\n- Complex multi-step work requiring planning and tracking\n- When user provides multiple tasks or requests (numbered/comma-separated)\n- After receiving new instructions that require multiple steps\n- BEFORE starting work on any todo (mark as in-progress)\n- IMMEDIATELY after completing each todo (mark completed individually)\n- When breaking down larger tasks into smaller actionable steps\n- To give users visibility into your progress and planning\n\nWhen NOT to use:\n- Single, trivial tasks that can be completed in one step\n- Purely conversational/informational requests\n- When just reading files or performing simple searches\n\nCRITICAL workflow:\n1. Plan tasks by writing todo list with specific, actionable items\n2. Complete the work for that specific todo\n3. Mark that todo as completed IMMEDIATELY\n4. Move to next todo and repeat\n\nTodo states:\n- not-started: Todo not yet begun\n- in-progress: Currently working (limit ONE at a time)\n- completed: Finished successfully\n\nIMPORTANT: Mark todos completed as soon as they are done. Do not batch completions.",
|
|
2345
|
+
schema: zod.z.object({
|
|
2346
|
+
operation: zod.z.enum(["write", "read"]),
|
|
2347
|
+
todoList: zod.z.array(zod.z.object({
|
|
2348
|
+
id: zod.z.number().int().positive().describe("Sequential id starting from 1"),
|
|
2349
|
+
title: zod.z.string().min(1),
|
|
2350
|
+
description: zod.z.string().min(1),
|
|
2351
|
+
status: zod.z.enum(["not-started", "in-progress", "completed"]),
|
|
2352
|
+
evidence: zod.z.string().max(200).optional()
|
|
2353
|
+
})).optional()
|
|
2354
|
+
}),
|
|
2355
|
+
func: async ({ operation, todoList }) => {
|
|
1075
2356
|
const onEvent = manageTodo._stateRef?.__onEvent;
|
|
1076
2357
|
if (operation === "read") {
|
|
1077
2358
|
const list = stateRef.todoList || [];
|
|
@@ -1088,27 +2369,16 @@ function createContextTools(stateRef, opts) {
|
|
|
1088
2369
|
};
|
|
1089
2370
|
onEvent?.({ type: "plan", source: "manage_todo_list", operation: "write", todoList });
|
|
1090
2371
|
return payload;
|
|
1091
|
-
},
|
|
1092
|
-
{
|
|
1093
|
-
name: "manage_todo_list",
|
|
1094
|
-
description: "Manage a structured todo list to track progress and plan tasks throughout your coding session. Use this tool VERY frequently to ensure task visibility and proper planning.\n\nWhen to use this tool:\n- Complex multi-step work requiring planning and tracking\n- When user provides multiple tasks or requests (numbered/comma-separated)\n- After receiving new instructions that require multiple steps\n- BEFORE starting work on any todo (mark as in-progress)\n- IMMEDIATELY after completing each todo (mark completed individually)\n- When breaking down larger tasks into smaller actionable steps\n- To give users visibility into your progress and planning\n\nWhen NOT to use:\n- Single, trivial tasks that can be completed in one step\n- Purely conversational/informational requests\n- When just reading files or performing simple searches\n\nCRITICAL workflow:\n1. Plan tasks by writing todo list with specific, actionable items\n2. Complete the work for that specific todo\n3. Mark that todo as completed IMMEDIATELY\n4. Move to next todo and repeat\n\nTodo states:\n- not-started: Todo not yet begun\n- in-progress: Currently working (limit ONE at a time)\n- completed: Finished successfully\n\nIMPORTANT: Mark todos completed as soon as they are done. Do not batch completions.",
|
|
1095
|
-
schema: import_zod2.z.object({
|
|
1096
|
-
operation: import_zod2.z.enum(["write", "read"]),
|
|
1097
|
-
todoList: import_zod2.z.array(import_zod2.z.object({
|
|
1098
|
-
id: import_zod2.z.number().int().positive().describe("Sequential id starting from 1"),
|
|
1099
|
-
title: import_zod2.z.string().min(1),
|
|
1100
|
-
description: import_zod2.z.string().min(1),
|
|
1101
|
-
status: import_zod2.z.enum(["not-started", "in-progress", "completed"]),
|
|
1102
|
-
evidence: import_zod2.z.string().max(200).optional()
|
|
1103
|
-
})).optional()
|
|
1104
|
-
})
|
|
1105
2372
|
}
|
|
1106
|
-
);
|
|
2373
|
+
});
|
|
1107
2374
|
manageTodo._stateRef = stateRef;
|
|
1108
2375
|
tools.push(manageTodo);
|
|
1109
2376
|
}
|
|
1110
|
-
const getTool = (
|
|
1111
|
-
|
|
2377
|
+
const getTool = createTool({
|
|
2378
|
+
name: "get_tool_response",
|
|
2379
|
+
description: "RETRIEVE tool execution response: Use this to access the full output of a tool execution that shows as 'SUMMARIZED' in the tool history.",
|
|
2380
|
+
schema: zod.z.object({ executionId: zod.z.string().describe("Tool execution id") }),
|
|
2381
|
+
func: async ({ executionId }) => {
|
|
1112
2382
|
let execution = stateRef.toolHistory?.find((t) => t.executionId === executionId);
|
|
1113
2383
|
if (!execution) {
|
|
1114
2384
|
execution = stateRef.toolHistoryArchived?.find((t) => t.executionId === executionId);
|
|
@@ -1117,32 +2387,24 @@ function createContextTools(stateRef, opts) {
|
|
|
1117
2387
|
return execution.rawOutput || execution.output;
|
|
1118
2388
|
}
|
|
1119
2389
|
return "Execution not found. Please check the executionId.";
|
|
1120
|
-
},
|
|
1121
|
-
{
|
|
1122
|
-
name: "get_tool_response",
|
|
1123
|
-
description: "RETRIEVE tool execution response: Use this to access the full output of a tool execution that shows as 'SUMMARIZED' in the tool history.",
|
|
1124
|
-
schema: import_zod2.z.object({ executionId: import_zod2.z.string().describe("Tool execution id") })
|
|
1125
2390
|
}
|
|
1126
|
-
);
|
|
2391
|
+
});
|
|
1127
2392
|
getTool._stateRef = stateRef;
|
|
1128
2393
|
tools.push(getTool);
|
|
1129
2394
|
if (opts?.outputSchema) {
|
|
1130
|
-
const responseTool = (
|
|
1131
|
-
|
|
2395
|
+
const responseTool = createTool({
|
|
2396
|
+
name: "response",
|
|
2397
|
+
description: "Finalize the answer by returning the final structured JSON matching the required schema. Call exactly once when you are fully done, then stop.",
|
|
2398
|
+
schema: opts.outputSchema.shape ? opts.outputSchema : zod.z.any(),
|
|
2399
|
+
func: async (data) => {
|
|
1132
2400
|
try {
|
|
1133
2401
|
const validated = opts.outputSchema.parse ? opts.outputSchema.parse(data) : data;
|
|
1134
2402
|
return { __finalStructuredOutput: true, data: validated };
|
|
1135
2403
|
} catch (e) {
|
|
1136
2404
|
return { error: "Schema validation failed", details: e?.message };
|
|
1137
2405
|
}
|
|
1138
|
-
},
|
|
1139
|
-
{
|
|
1140
|
-
name: "response",
|
|
1141
|
-
description: "Finalize the answer by returning the final structured JSON matching the required schema. Call exactly once when you are fully done, then stop.",
|
|
1142
|
-
// Use schema directly; if not Zod, fall back to any-object
|
|
1143
|
-
schema: opts.outputSchema.shape ? opts.outputSchema : import_zod2.z.any()
|
|
1144
2406
|
}
|
|
1145
|
-
);
|
|
2407
|
+
});
|
|
1146
2408
|
responseTool._stateRef = stateRef;
|
|
1147
2409
|
tools.push(responseTool);
|
|
1148
2410
|
}
|
|
@@ -1151,13 +2413,16 @@ function createContextTools(stateRef, opts) {
|
|
|
1151
2413
|
|
|
1152
2414
|
// src/graph/decisions.ts
|
|
1153
2415
|
function needsSummarization(state, opts, summarizationEnabled) {
|
|
1154
|
-
const maxTok = opts.limits?.maxToken;
|
|
1155
|
-
if (!maxTok || !summarizationEnabled)
|
|
2416
|
+
const maxTok = opts.limits?.maxToken ?? opts.limits?.contextTokenLimit;
|
|
2417
|
+
if (!maxTok || !summarizationEnabled) {
|
|
2418
|
+
return false;
|
|
2419
|
+
}
|
|
1156
2420
|
try {
|
|
1157
2421
|
const allText = (state.messages || []).map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("") : "").join("\n");
|
|
1158
2422
|
const tokenCount = countApproxTokens(allText);
|
|
1159
|
-
|
|
1160
|
-
|
|
2423
|
+
const needsSum = tokenCount > maxTok;
|
|
2424
|
+
return needsSum;
|
|
2425
|
+
} catch (e) {
|
|
1161
2426
|
return false;
|
|
1162
2427
|
}
|
|
1163
2428
|
}
|
|
@@ -1182,7 +2447,12 @@ function createSmartAgent(opts) {
|
|
|
1182
2447
|
const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
|
|
1183
2448
|
const mergedTools = [...opts.tools ?? [], ...contextTools];
|
|
1184
2449
|
const base = createAgent({ ...opts, tools: mergedTools });
|
|
1185
|
-
|
|
2450
|
+
let summarizationEnabled = false;
|
|
2451
|
+
if (typeof opts.summarization === "object") {
|
|
2452
|
+
summarizationEnabled = opts.summarization.enable !== false;
|
|
2453
|
+
} else {
|
|
2454
|
+
summarizationEnabled = opts.summarization !== false;
|
|
2455
|
+
}
|
|
1186
2456
|
const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
|
|
1187
2457
|
const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
|
|
1188
2458
|
const decideAfter = toolsDecisionFactory(opts, summarizationEnabled);
|
|
@@ -1219,6 +2489,14 @@ function createSmartAgent(opts) {
|
|
|
1219
2489
|
const res = await base.invoke(state, config);
|
|
1220
2490
|
lastResult = res;
|
|
1221
2491
|
state = res.state || { ...state, messages: res.messages };
|
|
2492
|
+
if (state.ctx?.__needsSummarization && summarizer) {
|
|
2493
|
+
const ctx = { ...state.ctx || {} };
|
|
2494
|
+
delete ctx.__needsSummarization;
|
|
2495
|
+
state = { ...state, ctx };
|
|
2496
|
+
const delta = await summarizer(state);
|
|
2497
|
+
state = { ...state, ...delta };
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
1222
2500
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
1223
2501
|
if (summarizationEnabled) {
|
|
1224
2502
|
const after = decideAfter(state);
|
|
@@ -1236,36 +2514,326 @@ function createSmartAgent(opts) {
|
|
|
1236
2514
|
}
|
|
1237
2515
|
return lastResult;
|
|
1238
2516
|
},
|
|
2517
|
+
snapshot: base.snapshot,
|
|
2518
|
+
resume: base.resume,
|
|
2519
|
+
resolveToolApproval: base.resolveToolApproval,
|
|
1239
2520
|
asTool: base.asTool,
|
|
1240
2521
|
asHandoff: base.asHandoff,
|
|
1241
2522
|
__runtime: base.__runtime
|
|
1242
2523
|
};
|
|
1243
2524
|
return instance;
|
|
1244
2525
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
2526
|
+
|
|
2527
|
+
// src/utils/content.ts
|
|
2528
|
+
function contentToString(content) {
|
|
2529
|
+
try {
|
|
2530
|
+
if (content == null) return "";
|
|
2531
|
+
if (typeof content === "string") return content;
|
|
2532
|
+
if (Array.isArray(content)) {
|
|
2533
|
+
return content.map((p) => {
|
|
2534
|
+
if (!p || typeof p !== "object") return String(p);
|
|
2535
|
+
if (p.type === "text") return String(p.text ?? "");
|
|
2536
|
+
if (p.type === "image_url") {
|
|
2537
|
+
const img = p.image_url;
|
|
2538
|
+
const url = typeof img === "string" ? img : img?.url;
|
|
2539
|
+
const detail = typeof img === "string" ? void 0 : img?.detail;
|
|
2540
|
+
return `[image:${url || "unknown"}${detail ? ` detail=${detail}` : ""}]`;
|
|
2541
|
+
}
|
|
2542
|
+
return JSON.stringify(p);
|
|
2543
|
+
}).join("\n");
|
|
2544
|
+
}
|
|
2545
|
+
return JSON.stringify(content);
|
|
2546
|
+
} catch {
|
|
2547
|
+
return String(content);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
function mergeContentsToString(contents) {
|
|
2551
|
+
return contents.map((c) => contentToString(c)).join("\n");
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// src/guardrails/checks.ts
|
|
2555
|
+
var Ajv = AjvModule__default.default?.default ?? AjvModule__default.default;
|
|
2556
|
+
var defaultAjv = new Ajv({ allErrors: true });
|
|
2557
|
+
defaultAjv.opts.strict = false;
|
|
2558
|
+
function ensureRegExp(pattern, flags) {
|
|
2559
|
+
if (pattern instanceof RegExp) return pattern;
|
|
2560
|
+
return new RegExp(pattern, flags ?? "i");
|
|
2561
|
+
}
|
|
2562
|
+
function latestText(context) {
|
|
2563
|
+
return contentToString(context.latestMessage?.content ?? "");
|
|
2564
|
+
}
|
|
2565
|
+
function threadText(context) {
|
|
2566
|
+
return mergeContentsToString(context.messages.map((msg) => msg.content));
|
|
2567
|
+
}
|
|
2568
|
+
function regexRule(options) {
|
|
2569
|
+
const regex = ensureRegExp(options.pattern, options.flags);
|
|
2570
|
+
const selector = options.selector || latestText;
|
|
2571
|
+
const failureMessage = options.failureMessage || `Message violates pattern: ${regex.toString()}`;
|
|
2572
|
+
const matchDisposition = options.matchDisposition || "block";
|
|
2573
|
+
const allowIfMatch = options.allowIfMatch === true;
|
|
2574
|
+
return {
|
|
2575
|
+
id: options.id,
|
|
2576
|
+
title: options.title || "Regex compliance",
|
|
2577
|
+
description: options.description,
|
|
2578
|
+
async evaluate(context) {
|
|
2579
|
+
const text = selector(context) ?? "";
|
|
2580
|
+
const matches = regex.test(text);
|
|
2581
|
+
const passed = allowIfMatch ? matches : !matches;
|
|
2582
|
+
const disposition = matches ? matchDisposition : "allow";
|
|
2583
|
+
return {
|
|
2584
|
+
passed,
|
|
2585
|
+
reason: matches ? failureMessage : void 0,
|
|
2586
|
+
disposition,
|
|
2587
|
+
details: matches ? { pattern: regex.toString(), textSnippet: text.slice(0, 200) } : void 0
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
function compileValidator(schema) {
|
|
2593
|
+
if (schema?._def) {
|
|
2594
|
+
const typed = schema;
|
|
2595
|
+
return (input) => {
|
|
2596
|
+
const result = typed.safeParse(input);
|
|
2597
|
+
return {
|
|
2598
|
+
valid: result.success,
|
|
2599
|
+
errors: result.success ? void 0 : result.error.issues.map((issue) => issue.message)
|
|
2600
|
+
};
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
const validator = defaultAjv.compile(schema);
|
|
2604
|
+
return (input) => {
|
|
2605
|
+
const valid = validator(input);
|
|
2606
|
+
const errors = validator.errors?.map((err) => {
|
|
2607
|
+
const path2 = "instancePath" in err && typeof err.instancePath === "string" && err.instancePath.length > 0 ? err.instancePath : err.dataPath;
|
|
2608
|
+
return `${path2 || "/"} ${err.message || "invalid"}`;
|
|
2609
|
+
});
|
|
2610
|
+
return { valid: !!valid, errors };
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
function jsonSchemaRule(options) {
|
|
2614
|
+
const selector = options.selector || latestText;
|
|
2615
|
+
const disposition = options.disposition || "block";
|
|
2616
|
+
const validator = compileValidator(options.schema);
|
|
2617
|
+
return {
|
|
2618
|
+
id: options.id,
|
|
2619
|
+
title: options.title || "JSON schema validation",
|
|
2620
|
+
description: options.description,
|
|
2621
|
+
async evaluate(context) {
|
|
2622
|
+
const text = selector(context) ?? "";
|
|
2623
|
+
let parsed;
|
|
2624
|
+
try {
|
|
2625
|
+
parsed = JSON.parse(text);
|
|
2626
|
+
} catch (err) {
|
|
2627
|
+
if (options.allowOnParseError) {
|
|
2628
|
+
return { passed: true };
|
|
2629
|
+
}
|
|
2630
|
+
return {
|
|
2631
|
+
passed: false,
|
|
2632
|
+
disposition,
|
|
2633
|
+
reason: "Message content is not valid JSON.",
|
|
2634
|
+
details: { error: err instanceof Error ? err.message : String(err) }
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
const result = validator(parsed);
|
|
2638
|
+
if (result.valid) {
|
|
2639
|
+
return { passed: true };
|
|
2640
|
+
}
|
|
2641
|
+
return {
|
|
2642
|
+
passed: false,
|
|
2643
|
+
disposition,
|
|
2644
|
+
reason: "Message JSON failed schema validation.",
|
|
2645
|
+
details: { errors: result.errors }
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
function containsAllowListed(text, allowList) {
|
|
2651
|
+
if (!allowList?.length) return false;
|
|
2652
|
+
return allowList.some((pattern) => ensureRegExp(pattern).test(text));
|
|
2653
|
+
}
|
|
2654
|
+
var codePattern = /```[\s\S]*?```|\b(function|class|import|export|const|let|var)\b|<[^>]+>/i;
|
|
2655
|
+
function codePresenceRule(options = {}) {
|
|
2656
|
+
const selector = options.selector || latestText;
|
|
2657
|
+
const disposition = options.disposition || "warn";
|
|
2658
|
+
return {
|
|
2659
|
+
id: options.id,
|
|
2660
|
+
title: options.title || "Code presence detection",
|
|
2661
|
+
description: options.description,
|
|
2662
|
+
async evaluate(context) {
|
|
2663
|
+
const text = selector(context) ?? "";
|
|
2664
|
+
if (!text) return { passed: true };
|
|
2665
|
+
if (containsAllowListed(text, options.allowList)) {
|
|
2666
|
+
return { passed: true };
|
|
2667
|
+
}
|
|
2668
|
+
const matches = codePattern.test(text);
|
|
2669
|
+
if (!matches) return { passed: true };
|
|
2670
|
+
return {
|
|
2671
|
+
passed: false,
|
|
2672
|
+
disposition,
|
|
2673
|
+
reason: "Potential code content detected in message.",
|
|
2674
|
+
details: { snippet: text.slice(0, 200) }
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
var guardianSchema = zod.z.object({
|
|
2680
|
+
allow: zod.z.boolean().describe("Whether the message should be allowed."),
|
|
2681
|
+
disposition: zod.z.enum(["allow", "warn", "block"]).optional(),
|
|
2682
|
+
reason: zod.z.string().max(400, "Reason must be 400 characters or fewer.").optional(),
|
|
2683
|
+
details: zod.z.record(zod.z.any()).optional()
|
|
1270
2684
|
});
|
|
2685
|
+
function defaultPayload(context) {
|
|
2686
|
+
const latest = latestText(context);
|
|
2687
|
+
const transcript = threadText(context);
|
|
2688
|
+
return JSON.stringify(
|
|
2689
|
+
{
|
|
2690
|
+
phase: context.phase,
|
|
2691
|
+
latestMessage: latest,
|
|
2692
|
+
transcript,
|
|
2693
|
+
metadata: {
|
|
2694
|
+
runtime: {
|
|
2695
|
+
name: context.runtime?.name,
|
|
2696
|
+
version: context.runtime?.version
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
},
|
|
2700
|
+
null,
|
|
2701
|
+
2
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2704
|
+
function agentVerdictRule(options) {
|
|
2705
|
+
const payloadBuilder = options.payloadBuilder || defaultPayload;
|
|
2706
|
+
const fallbackDisposition = options.dispositionFallback || "block";
|
|
2707
|
+
return {
|
|
2708
|
+
id: options.id,
|
|
2709
|
+
title: options.title || "Agent-based guardrail",
|
|
2710
|
+
description: options.description,
|
|
2711
|
+
async evaluate(context) {
|
|
2712
|
+
const payload = payloadBuilder(context);
|
|
2713
|
+
const systemPrompt = options.systemPrompt || "You are a safety auditor. Review the provided conversation payload and decide whether it should continue. Return JSON with keys allow (boolean), optional disposition ('allow' | 'warn' | 'block'), optional reason (string), and optional details (object).";
|
|
2714
|
+
const review = await options.agent.invoke({
|
|
2715
|
+
messages: [
|
|
2716
|
+
{ role: "system", content: systemPrompt },
|
|
2717
|
+
{ role: "user", content: payload }
|
|
2718
|
+
]
|
|
2719
|
+
});
|
|
2720
|
+
let parsed = review.output;
|
|
2721
|
+
if (!parsed) {
|
|
2722
|
+
try {
|
|
2723
|
+
parsed = guardianSchema.parse(JSON.parse(review.content));
|
|
2724
|
+
} catch {
|
|
2725
|
+
parsed = guardianSchema.parse({ allow: false, reason: "Guard agent did not return structured output." });
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
const disposition = parsed.disposition || (parsed.allow ? "allow" : fallbackDisposition);
|
|
2729
|
+
return {
|
|
2730
|
+
passed: parsed.allow,
|
|
2731
|
+
disposition,
|
|
2732
|
+
reason: parsed.reason,
|
|
2733
|
+
details: parsed.details
|
|
2734
|
+
};
|
|
2735
|
+
}
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2738
|
+
function customCallbackRule(options) {
|
|
2739
|
+
const fallbackDisposition = options.defaultDisposition || "block";
|
|
2740
|
+
return {
|
|
2741
|
+
id: options.id,
|
|
2742
|
+
title: options.title || "Custom callback guardrail",
|
|
2743
|
+
description: options.description,
|
|
2744
|
+
async evaluate(context) {
|
|
2745
|
+
const outcome = await options.callback(context);
|
|
2746
|
+
if (typeof outcome === "boolean") {
|
|
2747
|
+
return {
|
|
2748
|
+
passed: outcome,
|
|
2749
|
+
disposition: outcome ? "allow" : fallbackDisposition
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
return {
|
|
2753
|
+
passed: outcome.allow,
|
|
2754
|
+
disposition: outcome.disposition || (outcome.allow ? "allow" : fallbackDisposition),
|
|
2755
|
+
reason: outcome.reason,
|
|
2756
|
+
details: outcome.details
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
// src/guardrails/index.ts
|
|
2763
|
+
function createRegexGuardrail(pattern, options) {
|
|
2764
|
+
const rule = regexRule({ pattern, ...options?.rule || {} });
|
|
2765
|
+
return createGuardrail({
|
|
2766
|
+
id: options?.guardrailId,
|
|
2767
|
+
title: options?.guardrailTitle || options?.rule?.title || "Regex Guardrail",
|
|
2768
|
+
description: options?.guardrailDescription || options?.rule?.description,
|
|
2769
|
+
appliesTo: options?.phases,
|
|
2770
|
+
checks: [rule],
|
|
2771
|
+
haltOnViolation: options?.haltOnViolation,
|
|
2772
|
+
onViolation: options?.onViolation,
|
|
2773
|
+
metadata: options?.metadata
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
function createJsonGuardrail(schema, options) {
|
|
2777
|
+
const rule = jsonSchemaRule({ schema, ...options?.rule || {} });
|
|
2778
|
+
return createGuardrail({
|
|
2779
|
+
id: options?.guardrailId,
|
|
2780
|
+
title: options?.guardrailTitle || options?.rule?.title || "JSON Schema Guardrail",
|
|
2781
|
+
description: options?.guardrailDescription || options?.rule?.description,
|
|
2782
|
+
appliesTo: options?.phases,
|
|
2783
|
+
checks: [rule],
|
|
2784
|
+
haltOnViolation: options?.haltOnViolation,
|
|
2785
|
+
onViolation: options?.onViolation,
|
|
2786
|
+
metadata: options?.metadata
|
|
2787
|
+
});
|
|
2788
|
+
}
|
|
2789
|
+
function createCodeGuardrail(options) {
|
|
2790
|
+
const ruleOptions = { disposition: "warn", ...options?.rule || {} };
|
|
2791
|
+
const rule = codePresenceRule(ruleOptions);
|
|
2792
|
+
return createGuardrail({
|
|
2793
|
+
id: options?.guardrailId,
|
|
2794
|
+
title: options?.guardrailTitle || ruleOptions.title || "Code Presence Guardrail",
|
|
2795
|
+
description: options?.guardrailDescription || ruleOptions.description,
|
|
2796
|
+
appliesTo: options?.phases,
|
|
2797
|
+
checks: [rule],
|
|
2798
|
+
haltOnViolation: options?.haltOnViolation,
|
|
2799
|
+
onViolation: options?.onViolation,
|
|
2800
|
+
metadata: options?.metadata
|
|
2801
|
+
});
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
exports.GuardrailPhase = GuardrailPhase;
|
|
2805
|
+
exports.agentVerdictRule = agentVerdictRule;
|
|
2806
|
+
exports.applyTokenLimits = applyTokenLimits;
|
|
2807
|
+
exports.buildSystemPrompt = buildSystemPrompt;
|
|
2808
|
+
exports.captureSnapshot = captureSnapshot;
|
|
2809
|
+
exports.codePresenceRule = codePresenceRule;
|
|
2810
|
+
exports.cognipeerSink = cognipeerSink;
|
|
2811
|
+
exports.countApproxTokens = countApproxTokens;
|
|
2812
|
+
exports.createAgent = createAgent;
|
|
2813
|
+
exports.createAgentNode = createAgentNode;
|
|
2814
|
+
exports.createCodeGuardrail = createCodeGuardrail;
|
|
2815
|
+
exports.createContextSummarizeNode = createContextSummarizeNode;
|
|
2816
|
+
exports.createContextTools = createContextTools;
|
|
2817
|
+
exports.createGuardrail = createGuardrail;
|
|
2818
|
+
exports.createJsonGuardrail = createJsonGuardrail;
|
|
2819
|
+
exports.createRegexGuardrail = createRegexGuardrail;
|
|
2820
|
+
exports.createResolverNode = createResolverNode;
|
|
2821
|
+
exports.createSmartAgent = createSmartAgent;
|
|
2822
|
+
exports.createTool = createTool;
|
|
2823
|
+
exports.createToolLimitFinalizeNode = createToolLimitFinalizeNode;
|
|
2824
|
+
exports.createToolsNode = createToolsNode;
|
|
2825
|
+
exports.customCallbackRule = customCallbackRule;
|
|
2826
|
+
exports.customSink = customSink;
|
|
2827
|
+
exports.evaluateGuardrails = evaluateGuardrails;
|
|
2828
|
+
exports.fileSink = fileSink;
|
|
2829
|
+
exports.fromLangchainModel = fromLangchainModel;
|
|
2830
|
+
exports.fromLangchainTools = fromLangchainTools;
|
|
2831
|
+
exports.httpSink = httpSink;
|
|
2832
|
+
exports.isSmartModel = isSmartModel;
|
|
2833
|
+
exports.jsonSchemaRule = jsonSchemaRule;
|
|
2834
|
+
exports.regexRule = regexRule;
|
|
2835
|
+
exports.resolveToolApprovalState = resolveToolApprovalState;
|
|
2836
|
+
exports.restoreSnapshot = restoreSnapshot;
|
|
2837
|
+
exports.withTools = withTools;
|
|
2838
|
+
//# sourceMappingURL=index.cjs.map
|
|
1271
2839
|
//# sourceMappingURL=index.cjs.map
|