@cognipeer/agent-sdk 0.2.1 → 0.2.3
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/dist/index.cjs +447 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -4
- package/dist/index.d.ts +58 -4
- package/dist/index.mjs +447 -163
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -2
package/dist/index.cjs
CHANGED
|
@@ -143,7 +143,7 @@ function withTools(model, tools) {
|
|
|
143
143
|
function fromLangchainModel(lcModel) {
|
|
144
144
|
if (!lcModel) throw new Error("fromLangchainModel: model is undefined/null");
|
|
145
145
|
const adapted = {
|
|
146
|
-
invoke: async (messages) => {
|
|
146
|
+
invoke: async (messages, options) => {
|
|
147
147
|
const normalizeContent = (content) => {
|
|
148
148
|
if (content == null) return content;
|
|
149
149
|
if (typeof content === "string") return content;
|
|
@@ -176,7 +176,7 @@ function fromLangchainModel(lcModel) {
|
|
|
176
176
|
return { role, content: normalizeContent(m.content), name: m.name, tool_calls: m.tool_calls, tool_call_id: m.tool_call_id };
|
|
177
177
|
};
|
|
178
178
|
const lcMessages = messages.map(toLC);
|
|
179
|
-
const response = await lcModel.invoke(lcMessages);
|
|
179
|
+
const response = await lcModel.invoke(lcMessages, options);
|
|
180
180
|
if (response && typeof response === "object") {
|
|
181
181
|
const content = response.content ?? response.text ?? "";
|
|
182
182
|
return {
|
|
@@ -190,6 +190,43 @@ function fromLangchainModel(lcModel) {
|
|
|
190
190
|
}
|
|
191
191
|
return { role: "assistant", content: String(response ?? "") };
|
|
192
192
|
},
|
|
193
|
+
stream: async function* (messages, options) {
|
|
194
|
+
if (typeof lcModel.stream !== "function") return;
|
|
195
|
+
const normalizeContent = (content) => {
|
|
196
|
+
if (content == null) return content;
|
|
197
|
+
if (typeof content === "string") return content;
|
|
198
|
+
if (Array.isArray(content)) {
|
|
199
|
+
return content.map((part) => {
|
|
200
|
+
if (!part || typeof part !== "object") return part;
|
|
201
|
+
if (part.type === "text") return { type: "text", text: String(part.text ?? "") };
|
|
202
|
+
if (part.type === "image_url") {
|
|
203
|
+
const img = part.image_url;
|
|
204
|
+
if (typeof img === "string") return { type: "image_url", image_url: { url: img } };
|
|
205
|
+
if (img && typeof img === "object") {
|
|
206
|
+
if ("url" in img) return { type: "image_url", image_url: img };
|
|
207
|
+
if ("base64" in img) {
|
|
208
|
+
const media = img.media_type || "image/jpeg";
|
|
209
|
+
const dataUrl = `data:${media};base64,${img.base64}`;
|
|
210
|
+
const detail = img.detail;
|
|
211
|
+
return { type: "image_url", image_url: { url: dataUrl, detail } };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return part;
|
|
215
|
+
}
|
|
216
|
+
return part;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return content;
|
|
220
|
+
};
|
|
221
|
+
const toLC = (m) => {
|
|
222
|
+
if (m._getType || m.lc_serializable) return m;
|
|
223
|
+
return { role: m.role, content: normalizeContent(m.content), name: m.name, tool_calls: m.tool_calls, tool_call_id: m.tool_call_id };
|
|
224
|
+
};
|
|
225
|
+
const lcMessages = messages.map(toLC);
|
|
226
|
+
for await (const chunk of lcModel.stream(lcMessages, options)) {
|
|
227
|
+
yield chunk;
|
|
228
|
+
}
|
|
229
|
+
},
|
|
193
230
|
bindTools: (tools) => {
|
|
194
231
|
const lcReady = toLangchainTools(tools);
|
|
195
232
|
if (typeof lcModel.bindTools === "function") {
|
|
@@ -343,7 +380,7 @@ function safe(v) {
|
|
|
343
380
|
const n = Number(v);
|
|
344
381
|
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
345
382
|
}
|
|
346
|
-
var DEFAULT_COGNIPEER_URL = "https://
|
|
383
|
+
var DEFAULT_COGNIPEER_URL = "https://console.cognipeer.com/api/client/v1/tracing/sessions";
|
|
347
384
|
function fileSink(path2) {
|
|
348
385
|
return { type: "file", path: path2 };
|
|
349
386
|
}
|
|
@@ -401,133 +438,113 @@ function coerceToString(value) {
|
|
|
401
438
|
}
|
|
402
439
|
return void 0;
|
|
403
440
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
441
|
+
var METADATA_PATHS = {
|
|
442
|
+
model: [
|
|
443
|
+
"model",
|
|
444
|
+
"options.model",
|
|
445
|
+
"params.model",
|
|
446
|
+
"config.model",
|
|
447
|
+
"configuration.model",
|
|
448
|
+
"metadata.model",
|
|
449
|
+
"lc_kwargs.model",
|
|
450
|
+
"metadata.modelName",
|
|
451
|
+
"modelName",
|
|
452
|
+
"lc_alias",
|
|
453
|
+
"model_id",
|
|
454
|
+
"id",
|
|
455
|
+
"_model",
|
|
456
|
+
"_modelName",
|
|
457
|
+
"__model",
|
|
458
|
+
"_modelId",
|
|
459
|
+
"_llmType"
|
|
460
|
+
],
|
|
461
|
+
provider: [
|
|
462
|
+
"provider",
|
|
463
|
+
"options.provider",
|
|
464
|
+
"params.provider",
|
|
465
|
+
"config.provider",
|
|
466
|
+
"configuration.provider",
|
|
467
|
+
"metadata.provider",
|
|
468
|
+
"lc_kwargs.provider"
|
|
469
|
+
]
|
|
470
|
+
};
|
|
471
|
+
function getNestedValue(obj, path2) {
|
|
472
|
+
if (!obj || typeof path2 !== "string") return void 0;
|
|
473
|
+
return path2.split(".").reduce((current, key) => current?.[key], obj);
|
|
410
474
|
}
|
|
411
|
-
function
|
|
475
|
+
function extractMetadata(model, paths) {
|
|
412
476
|
if (!model) return void 0;
|
|
477
|
+
for (const path2 of paths) {
|
|
478
|
+
const value = getNestedValue(model, path2);
|
|
479
|
+
const str = coerceToString(value);
|
|
480
|
+
if (str) return str;
|
|
481
|
+
}
|
|
413
482
|
const maybeLc = model?._lc;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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;
|
|
483
|
+
if (maybeLc) {
|
|
484
|
+
for (const path2 of paths) {
|
|
485
|
+
const value = getNestedValue(maybeLc, path2);
|
|
486
|
+
const str = coerceToString(value);
|
|
487
|
+
if (str) return str;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
443
490
|
const maybeClient = model?.client || model?.api || model?.service;
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const
|
|
452
|
-
|
|
491
|
+
if (maybeClient) {
|
|
492
|
+
for (const path2 of paths) {
|
|
493
|
+
const value = getNestedValue(maybeClient, path2);
|
|
494
|
+
const str = coerceToString(value);
|
|
495
|
+
if (str) return str;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const constructorName = model?.constructor?.name;
|
|
499
|
+
if (constructorName && !["Object", "Function"].includes(constructorName)) {
|
|
500
|
+
return constructorName;
|
|
501
|
+
}
|
|
502
|
+
return void 0;
|
|
503
|
+
}
|
|
504
|
+
function getModelName(model) {
|
|
505
|
+
return extractMetadata(model, METADATA_PATHS.model);
|
|
453
506
|
}
|
|
454
507
|
function getProviderName(model) {
|
|
455
|
-
|
|
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
|
-
);
|
|
508
|
+
return extractMetadata(model, METADATA_PATHS.provider);
|
|
486
509
|
}
|
|
487
510
|
function inferModelFromMessages(messageList) {
|
|
488
511
|
if (!Array.isArray(messageList)) return void 0;
|
|
489
|
-
|
|
512
|
+
const messagePaths = [
|
|
513
|
+
"metadata.model",
|
|
514
|
+
"metadata.modelName",
|
|
515
|
+
"response_metadata.model",
|
|
516
|
+
"response_metadata.modelName",
|
|
517
|
+
"usage_metadata.model",
|
|
518
|
+
"model",
|
|
519
|
+
"modelName"
|
|
520
|
+
];
|
|
521
|
+
for (let i = messageList.length - 1; i >= 0; i--) {
|
|
490
522
|
const message = messageList[i];
|
|
491
523
|
if (!message) continue;
|
|
492
|
-
const
|
|
493
|
-
message
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
|
509
|
-
);
|
|
510
|
-
if (candidate) return candidate;
|
|
524
|
+
for (const path2 of messagePaths) {
|
|
525
|
+
const value = getNestedValue(message, path2);
|
|
526
|
+
const str = coerceToString(value);
|
|
527
|
+
if (str) return str;
|
|
528
|
+
}
|
|
511
529
|
}
|
|
512
530
|
return void 0;
|
|
513
531
|
}
|
|
514
532
|
function inferProviderFromMessages(messageList) {
|
|
515
533
|
if (!Array.isArray(messageList)) return void 0;
|
|
516
|
-
|
|
534
|
+
const messagePaths = [
|
|
535
|
+
"metadata.provider",
|
|
536
|
+
"response_metadata.provider",
|
|
537
|
+
"usage_metadata.provider",
|
|
538
|
+
"provider"
|
|
539
|
+
];
|
|
540
|
+
for (let i = messageList.length - 1; i >= 0; i--) {
|
|
517
541
|
const message = messageList[i];
|
|
518
542
|
if (!message) continue;
|
|
519
|
-
const
|
|
520
|
-
message
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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;
|
|
543
|
+
for (const path2 of messagePaths) {
|
|
544
|
+
const value = getNestedValue(message, path2);
|
|
545
|
+
const str = coerceToString(value);
|
|
546
|
+
if (str) return str;
|
|
547
|
+
}
|
|
531
548
|
}
|
|
532
549
|
return void 0;
|
|
533
550
|
}
|
|
@@ -625,20 +642,32 @@ async function postTraceSession(url, headers, payload) {
|
|
|
625
642
|
if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
|
|
626
643
|
finalHeaders["content-type"] = "application/json";
|
|
627
644
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
645
|
+
try {
|
|
646
|
+
const response = await fetchFn(url, {
|
|
647
|
+
method: "POST",
|
|
648
|
+
headers: finalHeaders,
|
|
649
|
+
body: JSON.stringify(payload)
|
|
650
|
+
});
|
|
651
|
+
if (!response.ok) {
|
|
652
|
+
let responseText = "";
|
|
653
|
+
try {
|
|
654
|
+
responseText = await response.text();
|
|
655
|
+
} catch {
|
|
656
|
+
}
|
|
657
|
+
const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
|
|
658
|
+
const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
|
|
659
|
+
const errorMsg = `${statusLine}${bodyPreview}`;
|
|
660
|
+
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
661
|
+
console.error("[Tracing] Error posting to", url, ":", errorMsg);
|
|
662
|
+
}
|
|
663
|
+
throw new Error(errorMsg);
|
|
638
664
|
}
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
|
|
665
|
+
} catch (err) {
|
|
666
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
667
|
+
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
668
|
+
console.error("[Tracing] Failed to post:", errMsg);
|
|
669
|
+
}
|
|
670
|
+
throw err;
|
|
642
671
|
}
|
|
643
672
|
}
|
|
644
673
|
function createEmptySummary() {
|
|
@@ -939,12 +968,16 @@ async function finalizeTraceSession(session, params = {}) {
|
|
|
939
968
|
const endedAtIso = new Date(endedAtMs).toISOString();
|
|
940
969
|
const durationMs = endedAtMs - session.startedAt;
|
|
941
970
|
const startedAtIso = new Date(session.startedAt).toISOString();
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
971
|
+
const buildAgentInfo = (runtime) => {
|
|
972
|
+
if (!runtime) return void 0;
|
|
973
|
+
return {
|
|
974
|
+
name: runtime.name || "unknown-agent",
|
|
975
|
+
version: runtime.version,
|
|
976
|
+
model: getModelName(runtime.model) || "unknown-model",
|
|
977
|
+
provider: getProviderName(runtime.model)
|
|
978
|
+
};
|
|
979
|
+
};
|
|
980
|
+
const agentInfo = buildAgentInfo(params.agentRuntime);
|
|
948
981
|
if (params.error) {
|
|
949
982
|
session.errors.push({
|
|
950
983
|
eventId: "session",
|
|
@@ -1112,12 +1145,29 @@ function createAgentCoreNode(opts) {
|
|
|
1112
1145
|
};
|
|
1113
1146
|
const normalizeBedrockToolPairing = (input) => {
|
|
1114
1147
|
const msgs = Array.isArray(input) ? [...input] : [];
|
|
1115
|
-
|
|
1116
|
-
|
|
1148
|
+
const validToolCallIds = /* @__PURE__ */ new Set();
|
|
1149
|
+
for (const m of msgs) {
|
|
1150
|
+
if (m?.role === "assistant") {
|
|
1151
|
+
for (const tu of extractToolUses(m)) {
|
|
1152
|
+
if (tu.id) validToolCallIds.add(tu.id);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
const filteredMsgs = msgs.filter((m) => {
|
|
1157
|
+
if (m?.role === "tool") {
|
|
1158
|
+
const toolCallId = m.tool_call_id;
|
|
1159
|
+
if (!toolCallId || !validToolCallIds.has(toolCallId)) {
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return true;
|
|
1164
|
+
});
|
|
1165
|
+
for (let i = 0; i < filteredMsgs.length; i++) {
|
|
1166
|
+
const m = filteredMsgs[i];
|
|
1117
1167
|
if (m?.role !== "assistant") continue;
|
|
1118
1168
|
const toolUses = extractToolUses(m);
|
|
1119
1169
|
if (toolUses.length === 0) continue;
|
|
1120
|
-
const next =
|
|
1170
|
+
const next = filteredMsgs[i + 1];
|
|
1121
1171
|
const firstId = toolUses[0]?.id;
|
|
1122
1172
|
const nextIsToolForFirst = !!(next && next.role === "tool" && next.tool_call_id === firstId);
|
|
1123
1173
|
if (nextIsToolForFirst) continue;
|
|
@@ -1125,12 +1175,12 @@ function createAgentCoreNode(opts) {
|
|
|
1125
1175
|
role: "tool",
|
|
1126
1176
|
name: tu.name || "unknown_tool",
|
|
1127
1177
|
tool_call_id: tu.id,
|
|
1128
|
-
content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for
|
|
1178
|
+
content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for tool_result adjacency."
|
|
1129
1179
|
}));
|
|
1130
|
-
|
|
1180
|
+
filteredMsgs.splice(i + 1, 0, ...placeholders);
|
|
1131
1181
|
i += placeholders.length;
|
|
1132
1182
|
}
|
|
1133
|
-
return
|
|
1183
|
+
return filteredMsgs;
|
|
1134
1184
|
};
|
|
1135
1185
|
const debugToolPairing = (messagesToSend) => {
|
|
1136
1186
|
try {
|
|
@@ -1152,11 +1202,54 @@ function createAgentCoreNode(opts) {
|
|
|
1152
1202
|
} catch (e) {
|
|
1153
1203
|
}
|
|
1154
1204
|
};
|
|
1205
|
+
const onEvent = state.ctx?.__onEvent;
|
|
1206
|
+
const onStream = state.ctx?.__onStream;
|
|
1207
|
+
const streamingEnabled = Boolean(state.ctx?.__streaming);
|
|
1208
|
+
const cancellationToken = state.ctx?.__cancellationToken;
|
|
1209
|
+
const abortSignal = state.ctx?.__abortSignal;
|
|
1210
|
+
const extractText = (chunk) => {
|
|
1211
|
+
if (chunk == null) return "";
|
|
1212
|
+
if (typeof chunk === "string") return chunk;
|
|
1213
|
+
if (typeof chunk?.content === "string") return chunk.content;
|
|
1214
|
+
if (Array.isArray(chunk?.content)) {
|
|
1215
|
+
return chunk.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
|
|
1216
|
+
}
|
|
1217
|
+
if (typeof chunk?.text === "string") return chunk.text;
|
|
1218
|
+
if (typeof chunk?.delta?.content === "string") return chunk.delta.content;
|
|
1219
|
+
return "";
|
|
1220
|
+
};
|
|
1155
1221
|
let response;
|
|
1156
1222
|
try {
|
|
1157
1223
|
const normalizedMessages = normalizeBedrockToolPairing([...state.messages]);
|
|
1158
1224
|
debugToolPairing(normalizedMessages);
|
|
1159
|
-
|
|
1225
|
+
if (streamingEnabled && typeof modelWithTools.stream === "function") {
|
|
1226
|
+
let streamedText = "";
|
|
1227
|
+
let streamedMessage;
|
|
1228
|
+
for await (const chunk of modelWithTools.stream(normalizedMessages, { signal: abortSignal, cancellationToken })) {
|
|
1229
|
+
if (cancellationToken && cancellationToken.isCancellationRequested || abortSignal?.aborted) {
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
if (chunk && typeof chunk === "object" && chunk.role) {
|
|
1233
|
+
streamedMessage = chunk;
|
|
1234
|
+
}
|
|
1235
|
+
const text = extractText(chunk);
|
|
1236
|
+
if (text) {
|
|
1237
|
+
streamedText += text;
|
|
1238
|
+
onStream?.({ text });
|
|
1239
|
+
onEvent?.({ type: "stream", text });
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (streamedMessage) {
|
|
1243
|
+
response = { ...streamedMessage };
|
|
1244
|
+
if (response.content == null || response.content === "") {
|
|
1245
|
+
response.content = streamedText;
|
|
1246
|
+
}
|
|
1247
|
+
} else {
|
|
1248
|
+
response = { role: "assistant", content: streamedText };
|
|
1249
|
+
}
|
|
1250
|
+
} else {
|
|
1251
|
+
response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken });
|
|
1252
|
+
}
|
|
1160
1253
|
} catch (err) {
|
|
1161
1254
|
const durationMs2 = Date.now() - start;
|
|
1162
1255
|
recordTraceEvent(traceSession, {
|
|
@@ -1177,7 +1270,7 @@ function createAgentCoreNode(opts) {
|
|
|
1177
1270
|
...state.messages,
|
|
1178
1271
|
response
|
|
1179
1272
|
];
|
|
1180
|
-
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.usage;
|
|
1273
|
+
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
|
|
1181
1274
|
const normalized = normalizeUsage(rawUsage);
|
|
1182
1275
|
const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
|
|
1183
1276
|
const durationMs = Date.now() - start;
|
|
@@ -1244,6 +1337,9 @@ function createToolsNode(initialTools, opts) {
|
|
|
1244
1337
|
};
|
|
1245
1338
|
const appended = [];
|
|
1246
1339
|
const onEvent = state.ctx?.__onEvent;
|
|
1340
|
+
const onProgress = state.ctx?.__onProgress;
|
|
1341
|
+
const cancellationToken = state.ctx?.__cancellationToken;
|
|
1342
|
+
const abortSignal = state.ctx?.__abortSignal;
|
|
1247
1343
|
const traceSession = state.ctx?.__traceSession;
|
|
1248
1344
|
const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
|
|
1249
1345
|
const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
|
|
@@ -1300,7 +1396,20 @@ function createToolsNode(initialTools, opts) {
|
|
|
1300
1396
|
name: tc.name
|
|
1301
1397
|
});
|
|
1302
1398
|
}
|
|
1399
|
+
const isCancelled = () => {
|
|
1400
|
+
if (abortSignal?.aborted) return { cancelled: true, reason: "aborted" };
|
|
1401
|
+
if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
|
|
1402
|
+
return { cancelled: false, reason: void 0 };
|
|
1403
|
+
};
|
|
1303
1404
|
const runOne = async (tc) => {
|
|
1405
|
+
const cancelState = isCancelled();
|
|
1406
|
+
if (cancelState.cancelled) {
|
|
1407
|
+
const ctx = state.ctx = state.ctx || {};
|
|
1408
|
+
ctx.__cancelled = { stage: "tools", reason: cancelState.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1409
|
+
onEvent?.({ type: "cancelled", stage: "tools", reason: cancelState.reason });
|
|
1410
|
+
onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
|
|
1411
|
+
return { status: "error" };
|
|
1412
|
+
}
|
|
1304
1413
|
const t = toolByName.get(tc.name);
|
|
1305
1414
|
if (!t) {
|
|
1306
1415
|
onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
|
|
@@ -1379,15 +1488,17 @@ function createToolsNode(initialTools, opts) {
|
|
|
1379
1488
|
const start = Date.now();
|
|
1380
1489
|
try {
|
|
1381
1490
|
onEvent?.({ type: "tool_call", phase: "start", name: t.name, id: tc.id, args });
|
|
1491
|
+
onProgress?.({ stage: "tools", message: `Running tool ${tc.name}` });
|
|
1382
1492
|
const sanitizedArgs = sanitizeTracePayload(args);
|
|
1383
1493
|
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1384
1494
|
let output;
|
|
1385
1495
|
const anyTool = t;
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
else if (typeof anyTool.
|
|
1389
|
-
else if (typeof anyTool.
|
|
1390
|
-
else if (typeof anyTool.
|
|
1496
|
+
const callOptions = { cancellationToken, signal: abortSignal };
|
|
1497
|
+
if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
|
|
1498
|
+
else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
|
|
1499
|
+
else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
|
|
1500
|
+
else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
|
|
1501
|
+
else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
|
|
1391
1502
|
else throw new Error("Tool is not invokable");
|
|
1392
1503
|
if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
|
|
1393
1504
|
const newRuntime = output.__handoff.runtime;
|
|
@@ -1417,6 +1528,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1417
1528
|
toolHistory.push({ executionId, toolName: t.name, args, output, rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
|
|
1418
1529
|
appended.push({ role: "tool", content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1419
1530
|
onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: output, durationMs });
|
|
1531
|
+
onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
|
|
1420
1532
|
if (needsApproval && approvalEntry) {
|
|
1421
1533
|
approvalEntry.status = "executed";
|
|
1422
1534
|
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1466,6 +1578,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1466
1578
|
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 });
|
|
1467
1579
|
appended.push({ role: "tool", content: `Error executing tool: ${e?.message || String(e)}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1468
1580
|
onEvent?.({ type: "tool_call", phase: "error", name: t.name, id: tc.id, args, error: { message: e?.message || String(e) } });
|
|
1581
|
+
onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: e?.message || String(e) } });
|
|
1469
1582
|
const sanitizedArgs = sanitizeTracePayload(args);
|
|
1470
1583
|
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1471
1584
|
const toolName = t.name || tc.name;
|
|
@@ -1509,6 +1622,14 @@ function createToolsNode(initialTools, opts) {
|
|
|
1509
1622
|
};
|
|
1510
1623
|
for (const tc of planned) {
|
|
1511
1624
|
if (awaitingApproval) break;
|
|
1625
|
+
const cancelState = isCancelled();
|
|
1626
|
+
if (cancelState.cancelled) {
|
|
1627
|
+
const ctx = state.ctx = state.ctx || {};
|
|
1628
|
+
ctx.__cancelled = { stage: "tools", reason: cancelState.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1629
|
+
onEvent?.({ type: "cancelled", stage: "tools", reason: cancelState.reason });
|
|
1630
|
+
onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
|
|
1631
|
+
break;
|
|
1632
|
+
}
|
|
1512
1633
|
const result = await runOne(tc);
|
|
1513
1634
|
if (result?.status === "awaiting_approval") {
|
|
1514
1635
|
awaitingApproval = true;
|
|
@@ -1536,7 +1657,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1536
1657
|
// src/nodes/toolLimitFinalize.ts
|
|
1537
1658
|
function createToolLimitFinalizeNode(_opts) {
|
|
1538
1659
|
return async (state) => {
|
|
1539
|
-
const notice = { role: "
|
|
1660
|
+
const notice = { role: "user", content: "[System Notice] Tool-call limit reached. Produce the best possible final answer using the available context and prior tool outputs. Do not call any more tools." };
|
|
1540
1661
|
const ctx = { ...state.ctx || {}, __finalizedDueToToolLimit: true };
|
|
1541
1662
|
return { messages: [...state.messages, notice], ctx };
|
|
1542
1663
|
};
|
|
@@ -1813,12 +1934,37 @@ function createAgent(opts) {
|
|
|
1813
1934
|
delete nextCtx.__resumeStage;
|
|
1814
1935
|
state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
|
|
1815
1936
|
}
|
|
1816
|
-
const
|
|
1937
|
+
const mergedLimits = {
|
|
1938
|
+
...opts.limits || {},
|
|
1939
|
+
...config?.limits || {}
|
|
1940
|
+
};
|
|
1941
|
+
const maxToolCalls = mergedLimits?.maxToolCalls === void 0 ? 50 : mergedLimits?.maxToolCalls;
|
|
1817
1942
|
const iterationLimit = maxToolCalls === Infinity ? 100 : Math.max(maxToolCalls * 3 + 10, 40);
|
|
1818
1943
|
let iterations = 0;
|
|
1819
1944
|
const onStateChange = config?.onStateChange;
|
|
1820
1945
|
const checkpointReason = config?.checkpointReason;
|
|
1821
1946
|
let pausedStage = null;
|
|
1947
|
+
const onProgress = state.ctx?.__onProgress;
|
|
1948
|
+
const isCancelled = () => {
|
|
1949
|
+
const ctx = state.ctx || {};
|
|
1950
|
+
const token = ctx.__cancellationToken;
|
|
1951
|
+
const signal = ctx.__abortSignal;
|
|
1952
|
+
const deadline = ctx.__deadline;
|
|
1953
|
+
if (signal?.aborted) return { cancelled: true, reason: "aborted" };
|
|
1954
|
+
if (token && token.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
|
|
1955
|
+
if (deadline && Date.now() > deadline) return { cancelled: true, reason: "timeout" };
|
|
1956
|
+
return { cancelled: false, reason: void 0 };
|
|
1957
|
+
};
|
|
1958
|
+
const cancelIfRequested = (stage) => {
|
|
1959
|
+
const result = isCancelled();
|
|
1960
|
+
if (!result.cancelled) return false;
|
|
1961
|
+
const ctx = { ...state.ctx || {} };
|
|
1962
|
+
ctx.__cancelled = { stage, reason: result.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1963
|
+
state = { ...state, ctx };
|
|
1964
|
+
emit?.({ type: "cancelled", stage, reason: result.reason });
|
|
1965
|
+
onProgress?.({ stage, message: "Cancelled", detail: { reason: result.reason } });
|
|
1966
|
+
return true;
|
|
1967
|
+
};
|
|
1822
1968
|
const checkpointIfRequested = (stage) => {
|
|
1823
1969
|
if (typeof onStateChange !== "function") return false;
|
|
1824
1970
|
let result = false;
|
|
@@ -1841,8 +1987,10 @@ function createAgent(opts) {
|
|
|
1841
1987
|
};
|
|
1842
1988
|
while (iterations < iterationLimit) {
|
|
1843
1989
|
iterations++;
|
|
1990
|
+
if (cancelIfRequested("loop")) break;
|
|
1844
1991
|
const skippingAgent = resumeStage === "tools";
|
|
1845
1992
|
if (!skippingAgent) {
|
|
1993
|
+
if (cancelIfRequested("before_guardrails")) break;
|
|
1846
1994
|
if (checkpointIfRequested("before_guardrails")) break;
|
|
1847
1995
|
const preGuardrails = getGuardrailConfig(state);
|
|
1848
1996
|
if (preGuardrails.length > 0) {
|
|
@@ -1897,7 +2045,10 @@ function createAgent(opts) {
|
|
|
1897
2045
|
break;
|
|
1898
2046
|
}
|
|
1899
2047
|
}
|
|
2048
|
+
onProgress?.({ stage: "agent", message: "Invoking model" });
|
|
2049
|
+
if (cancelIfRequested("before_agent")) break;
|
|
1900
2050
|
state = { ...state, ...await agentCore(state) };
|
|
2051
|
+
onProgress?.({ stage: "agent", message: "Model response received" });
|
|
1901
2052
|
if (checkpointIfRequested("after_agent")) break;
|
|
1902
2053
|
const postGuardrails = getGuardrailConfig(state);
|
|
1903
2054
|
if (postGuardrails.length > 0) {
|
|
@@ -1965,7 +2116,10 @@ function createAgent(opts) {
|
|
|
1965
2116
|
continue;
|
|
1966
2117
|
}
|
|
1967
2118
|
if (toolCalls.length === 0) break;
|
|
2119
|
+
onProgress?.({ stage: "tools", message: "Running tools" });
|
|
2120
|
+
if (cancelIfRequested("before_tools")) break;
|
|
1968
2121
|
state = { ...state, ...await toolsNode(state) };
|
|
2122
|
+
onProgress?.({ stage: "tools", message: "Tools finished" });
|
|
1969
2123
|
if (state.ctx?.__awaitingApproval) break;
|
|
1970
2124
|
if (checkpointIfRequested("after_tools")) break;
|
|
1971
2125
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
@@ -2006,15 +2160,39 @@ function createAgent(opts) {
|
|
|
2006
2160
|
}
|
|
2007
2161
|
const invokeAgent = async (input, config) => {
|
|
2008
2162
|
const onEvent = config?.onEvent;
|
|
2163
|
+
const onProgress = config?.onProgress;
|
|
2164
|
+
const onStream = config?.onStream;
|
|
2165
|
+
const streamEnabled = config?.stream === true;
|
|
2009
2166
|
const emit = (e) => {
|
|
2010
2167
|
try {
|
|
2011
2168
|
onEvent?.(e);
|
|
2012
2169
|
} catch {
|
|
2013
2170
|
}
|
|
2014
2171
|
};
|
|
2172
|
+
const emitProgress = (progress) => {
|
|
2173
|
+
try {
|
|
2174
|
+
onProgress?.(progress);
|
|
2175
|
+
} catch {
|
|
2176
|
+
}
|
|
2177
|
+
emit({ type: "progress", ...progress });
|
|
2178
|
+
};
|
|
2015
2179
|
const traceSession = createTraceSession(opts);
|
|
2016
|
-
const ctx = {
|
|
2180
|
+
const ctx = {
|
|
2181
|
+
...input.ctx || {},
|
|
2182
|
+
__onEvent: onEvent,
|
|
2183
|
+
__onProgress: emitProgress,
|
|
2184
|
+
__onStream: onStream,
|
|
2185
|
+
__streaming: streamEnabled
|
|
2186
|
+
};
|
|
2017
2187
|
if (traceSession) ctx.__traceSession = traceSession;
|
|
2188
|
+
if (config?.cancellationToken) ctx.__cancellationToken = config.cancellationToken;
|
|
2189
|
+
if (config?.cancellationToken?.aborted !== void 0) {
|
|
2190
|
+
ctx.__abortSignal = config?.cancellationToken;
|
|
2191
|
+
}
|
|
2192
|
+
if (config?.timeoutMs && config.timeoutMs > 0) {
|
|
2193
|
+
ctx.__deadline = Date.now() + config.timeoutMs;
|
|
2194
|
+
}
|
|
2195
|
+
const runtimeWithInvokeLimits = config?.limits ? { ...runtime, limits: { ...runtime.limits || {}, ...config.limits } } : runtime;
|
|
2018
2196
|
const initial = {
|
|
2019
2197
|
messages: input.messages || [],
|
|
2020
2198
|
toolCallCount: input.toolCallCount || 0,
|
|
@@ -2023,7 +2201,7 @@ function createAgent(opts) {
|
|
|
2023
2201
|
metadata: input.metadata,
|
|
2024
2202
|
ctx,
|
|
2025
2203
|
pendingApprovals: input.pendingApprovals || [],
|
|
2026
|
-
agent: input.agent ||
|
|
2204
|
+
agent: input.agent || runtimeWithInvokeLimits,
|
|
2027
2205
|
usage: input.usage || { perRequest: [], totals: {} }
|
|
2028
2206
|
};
|
|
2029
2207
|
let res;
|
|
@@ -2066,6 +2244,10 @@ function createAgent(opts) {
|
|
|
2066
2244
|
}
|
|
2067
2245
|
}
|
|
2068
2246
|
emit({ type: "finalAnswer", content: typeof finalMsg?.content === "string" ? finalMsg.content : content });
|
|
2247
|
+
if (streamEnabled && content) {
|
|
2248
|
+
onStream?.({ text: content, isFinal: true });
|
|
2249
|
+
emit({ type: "stream", text: content, isFinal: true });
|
|
2250
|
+
}
|
|
2069
2251
|
return {
|
|
2070
2252
|
content,
|
|
2071
2253
|
output: parsed,
|
|
@@ -2236,16 +2418,47 @@ function createAgentNode(opts) {
|
|
|
2236
2418
|
// src/nodes/contextSummarize.ts
|
|
2237
2419
|
var systemMessage = (content) => ({ role: "system", content });
|
|
2238
2420
|
var humanMessage = (content) => ({ role: "user", content });
|
|
2421
|
+
function validateMessageSequence(messages) {
|
|
2422
|
+
const validated = [];
|
|
2423
|
+
const pendingToolCallIds = /* @__PURE__ */ new Set();
|
|
2424
|
+
for (const m of messages) {
|
|
2425
|
+
if (m.role === "assistant") {
|
|
2426
|
+
if (!m.tool_calls || m.tool_calls.length === 0) {
|
|
2427
|
+
pendingToolCallIds.clear();
|
|
2428
|
+
} else {
|
|
2429
|
+
m.tool_calls.forEach((tc) => {
|
|
2430
|
+
if (tc.id) pendingToolCallIds.add(tc.id);
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
validated.push(m);
|
|
2434
|
+
} else if (m.role === "tool") {
|
|
2435
|
+
const toolCallId = m.tool_call_id;
|
|
2436
|
+
if (toolCallId && pendingToolCallIds.has(toolCallId)) {
|
|
2437
|
+
pendingToolCallIds.delete(toolCallId);
|
|
2438
|
+
validated.push(m);
|
|
2439
|
+
}
|
|
2440
|
+
} else {
|
|
2441
|
+
validated.push(m);
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
return validated;
|
|
2445
|
+
}
|
|
2239
2446
|
function getSummarizationConfig(opts) {
|
|
2240
2447
|
if (typeof opts.summarization === "object") {
|
|
2241
2448
|
return {
|
|
2242
2449
|
enabled: opts.summarization.enable !== false,
|
|
2243
|
-
maxTokens: opts.summarization.maxTokens
|
|
2450
|
+
maxTokens: opts.summarization.maxTokens ?? 5e4,
|
|
2451
|
+
// Max tokens we can send to the model for summarization prompt itself
|
|
2452
|
+
// This should be well under the model's context limit
|
|
2453
|
+
summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3,
|
|
2454
|
+
promptTemplate: opts.summarization.promptTemplate
|
|
2244
2455
|
};
|
|
2245
2456
|
}
|
|
2246
2457
|
return {
|
|
2247
2458
|
enabled: opts.summarization !== false,
|
|
2248
|
-
maxTokens: 5e4
|
|
2459
|
+
maxTokens: 5e4,
|
|
2460
|
+
summaryPromptMaxTokens: 8e3,
|
|
2461
|
+
promptTemplate: void 0
|
|
2249
2462
|
};
|
|
2250
2463
|
}
|
|
2251
2464
|
function createContextSummarizeNode(opts) {
|
|
@@ -2258,31 +2471,53 @@ function createContextSummarizeNode(opts) {
|
|
|
2258
2471
|
if (compressableMessages.length === 0) {
|
|
2259
2472
|
return {};
|
|
2260
2473
|
}
|
|
2261
|
-
const
|
|
2474
|
+
const summaryPromptMaxTokens = config.summaryPromptMaxTokens;
|
|
2475
|
+
const availableTokensForConversation = summaryPromptMaxTokens - 500;
|
|
2476
|
+
const messageTexts = [];
|
|
2477
|
+
let accumulatedTokens = 0;
|
|
2478
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2479
|
+
const m = messages[i];
|
|
2262
2480
|
let content = "";
|
|
2263
2481
|
if (typeof m.content === "string") content = m.content;
|
|
2264
2482
|
else if (Array.isArray(m.content)) {
|
|
2265
2483
|
content = m.content.map((c) => c.text || JSON.stringify(c)).join(" ");
|
|
2266
2484
|
}
|
|
2485
|
+
const maxContentLength = 2e3;
|
|
2486
|
+
if (content.length > maxContentLength) {
|
|
2487
|
+
content = content.substring(0, maxContentLength) + "... [TRUNCATED]";
|
|
2488
|
+
}
|
|
2267
2489
|
let prefix = `${m.role.toUpperCase()}`;
|
|
2268
2490
|
if (m.name) prefix += ` (${m.name})`;
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2491
|
+
const messageText = `${prefix}: ${content}`;
|
|
2492
|
+
const messageTokens = countApproxTokens(messageText);
|
|
2493
|
+
if (accumulatedTokens + messageTokens > availableTokensForConversation) {
|
|
2494
|
+
messageTexts.unshift("[Earlier conversation history omitted for brevity]");
|
|
2495
|
+
break;
|
|
2496
|
+
}
|
|
2497
|
+
messageTexts.unshift(messageText);
|
|
2498
|
+
accumulatedTokens += messageTokens;
|
|
2499
|
+
}
|
|
2500
|
+
const conversationText = messageTexts.join("\n\n");
|
|
2501
|
+
const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
|
|
2502
|
+
const defaultPrompt = `Please summarize the following conversation history and update any previous summary.
|
|
2275
2503
|
Focus on:
|
|
2276
2504
|
- User goals and intent.
|
|
2277
2505
|
- Key decisions made and actions taken.
|
|
2278
2506
|
- Important tool outputs and data retrieved.
|
|
2279
2507
|
- Current state of the task.
|
|
2280
2508
|
|
|
2509
|
+
Previous summary (if any):
|
|
2510
|
+
${previousSummary || "(none)"}
|
|
2511
|
+
|
|
2281
2512
|
Conversation:
|
|
2282
2513
|
${conversationText}
|
|
2283
2514
|
|
|
2284
|
-
Summary
|
|
2285
|
-
|
|
2515
|
+
Summary:`;
|
|
2516
|
+
const template = config.promptTemplate || defaultPrompt;
|
|
2517
|
+
const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
|
|
2518
|
+
const summaryPrompt = [
|
|
2519
|
+
systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
|
|
2520
|
+
humanMessage(promptBody)
|
|
2286
2521
|
];
|
|
2287
2522
|
let summaryText = "Summary unavailable.";
|
|
2288
2523
|
try {
|
|
@@ -2292,8 +2527,22 @@ Summary:`
|
|
|
2292
2527
|
console.error("[ContextSummarize] Failed to generate summary:", err);
|
|
2293
2528
|
return {};
|
|
2294
2529
|
}
|
|
2530
|
+
const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
|
|
2531
|
+
messages.forEach((m, idx) => {
|
|
2532
|
+
if (m.role === "assistant" && m.tool_calls && Array.isArray(m.tool_calls)) {
|
|
2533
|
+
m.tool_calls.forEach((tc) => {
|
|
2534
|
+
if (tc.id) {
|
|
2535
|
+
toolCallIdToAssistantIdx.set(tc.id, idx);
|
|
2536
|
+
}
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
});
|
|
2295
2540
|
const newMessages = messages.map((m) => {
|
|
2296
2541
|
if (m.role === "tool") {
|
|
2542
|
+
const toolCallId = m.tool_call_id;
|
|
2543
|
+
if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
|
|
2544
|
+
return null;
|
|
2545
|
+
}
|
|
2297
2546
|
if (m.content === "SUMMARIZED") return m;
|
|
2298
2547
|
return {
|
|
2299
2548
|
...m,
|
|
@@ -2301,7 +2550,8 @@ Summary:`
|
|
|
2301
2550
|
};
|
|
2302
2551
|
}
|
|
2303
2552
|
return m;
|
|
2304
|
-
});
|
|
2553
|
+
}).filter((m) => m !== null);
|
|
2554
|
+
const validatedMessages = validateMessageSequence(newMessages);
|
|
2305
2555
|
const summaryToolCallId = `call_summary_${Date.now()}`;
|
|
2306
2556
|
const assistantSummaryCall = {
|
|
2307
2557
|
role: "assistant",
|
|
@@ -2325,8 +2575,10 @@ Summary:`
|
|
|
2325
2575
|
// Matches the tool name in the call
|
|
2326
2576
|
content: summaryText
|
|
2327
2577
|
};
|
|
2578
|
+
const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
|
|
2328
2579
|
return {
|
|
2329
|
-
messages: [...
|
|
2580
|
+
messages: [...validatedMessages, assistantSummaryCall, toolSummaryResponse],
|
|
2581
|
+
summaries
|
|
2330
2582
|
};
|
|
2331
2583
|
};
|
|
2332
2584
|
}
|
|
@@ -2431,9 +2683,29 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
2431
2683
|
maxTok = 5e4;
|
|
2432
2684
|
}
|
|
2433
2685
|
try {
|
|
2434
|
-
const allText = (state.messages || []).map((m) =>
|
|
2686
|
+
const allText = (state.messages || []).map((m) => {
|
|
2687
|
+
if (typeof m.content === "string") return m.content;
|
|
2688
|
+
if (Array.isArray(m.content)) {
|
|
2689
|
+
return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
|
|
2690
|
+
}
|
|
2691
|
+
if (m.content && typeof m.content === "object") {
|
|
2692
|
+
return JSON.stringify(m.content);
|
|
2693
|
+
}
|
|
2694
|
+
return "";
|
|
2695
|
+
}).join("\n");
|
|
2435
2696
|
const tokenCount = countApproxTokens(allText);
|
|
2436
2697
|
const needsSum = tokenCount > maxTok;
|
|
2698
|
+
if (process.env.DEBUG_SUMMARIZATION) {
|
|
2699
|
+
const msgCount = (state.messages || []).length;
|
|
2700
|
+
const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
|
|
2701
|
+
const toolDetails = toolMsgs.map((m, i) => {
|
|
2702
|
+
const contentStr = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
2703
|
+
return `[${i}] id:${m.tool_call_id?.slice(-8) || "none"} len:${contentStr.length} preview:"${contentStr.slice(0, 30)}..."`;
|
|
2704
|
+
});
|
|
2705
|
+
console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
|
|
2706
|
+
toolDetails.forEach((d) => console.log(` ${d}`));
|
|
2707
|
+
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
|
|
2708
|
+
}
|
|
2437
2709
|
return needsSum;
|
|
2438
2710
|
} catch (e) {
|
|
2439
2711
|
return false;
|
|
@@ -2491,7 +2763,8 @@ function createSmartAgent(opts) {
|
|
|
2491
2763
|
const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
|
|
2492
2764
|
let state = { ...input, messages: seedMessages };
|
|
2493
2765
|
let lastResult = null;
|
|
2494
|
-
const
|
|
2766
|
+
const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? opts.limits?.maxToolCalls ?? 10;
|
|
2767
|
+
const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
|
|
2495
2768
|
for (let i = 0; i < iterationLimit; i++) {
|
|
2496
2769
|
const next = summarizationEnabled ? decideBefore(state) : "agent";
|
|
2497
2770
|
if (next === "contextSummarize" && summarizer) {
|
|
@@ -2503,7 +2776,11 @@ function createSmartAgent(opts) {
|
|
|
2503
2776
|
}
|
|
2504
2777
|
const res = await base.invoke(state, config);
|
|
2505
2778
|
lastResult = res;
|
|
2779
|
+
const currentSummaries = state.summaries;
|
|
2506
2780
|
state = res.state || { ...state, messages: res.messages };
|
|
2781
|
+
if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
|
|
2782
|
+
state = { ...state, summaries: currentSummaries };
|
|
2783
|
+
}
|
|
2507
2784
|
if (state.ctx?.__needsSummarization && summarizer) {
|
|
2508
2785
|
const ctx = { ...state.ctx || {} };
|
|
2509
2786
|
delete ctx.__needsSummarization;
|
|
@@ -2531,6 +2808,13 @@ function createSmartAgent(opts) {
|
|
|
2531
2808
|
const res = await base.invoke(state, config);
|
|
2532
2809
|
lastResult = res;
|
|
2533
2810
|
}
|
|
2811
|
+
if (state.summaries && state.summaries.length > 0) {
|
|
2812
|
+
if (lastResult.state) {
|
|
2813
|
+
lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
|
|
2814
|
+
} else {
|
|
2815
|
+
lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2534
2818
|
return lastResult;
|
|
2535
2819
|
},
|
|
2536
2820
|
snapshot: base.snapshot,
|