@cognipeer/agent-sdk 0.2.2 → 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 +423 -160
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -4
- package/dist/index.d.ts +56 -4
- package/dist/index.mjs +423 -160
- 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);
|
|
664
|
+
}
|
|
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);
|
|
638
669
|
}
|
|
639
|
-
|
|
640
|
-
const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
|
|
641
|
-
throw new Error(`${statusLine}${bodyPreview}`);
|
|
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,6 +2418,31 @@ 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 {
|
|
@@ -2243,13 +2450,15 @@ function getSummarizationConfig(opts) {
|
|
|
2243
2450
|
maxTokens: opts.summarization.maxTokens ?? 5e4,
|
|
2244
2451
|
// Max tokens we can send to the model for summarization prompt itself
|
|
2245
2452
|
// This should be well under the model's context limit
|
|
2246
|
-
summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3
|
|
2453
|
+
summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3,
|
|
2454
|
+
promptTemplate: opts.summarization.promptTemplate
|
|
2247
2455
|
};
|
|
2248
2456
|
}
|
|
2249
2457
|
return {
|
|
2250
2458
|
enabled: opts.summarization !== false,
|
|
2251
2459
|
maxTokens: 5e4,
|
|
2252
|
-
summaryPromptMaxTokens: 8e3
|
|
2460
|
+
summaryPromptMaxTokens: 8e3,
|
|
2461
|
+
promptTemplate: void 0
|
|
2253
2462
|
};
|
|
2254
2463
|
}
|
|
2255
2464
|
function createContextSummarizeNode(opts) {
|
|
@@ -2289,21 +2498,26 @@ function createContextSummarizeNode(opts) {
|
|
|
2289
2498
|
accumulatedTokens += messageTokens;
|
|
2290
2499
|
}
|
|
2291
2500
|
const conversationText = messageTexts.join("\n\n");
|
|
2292
|
-
const
|
|
2293
|
-
|
|
2294
|
-
humanMessage(
|
|
2295
|
-
`Please summarize the following conversation history.
|
|
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.
|
|
2296
2503
|
Focus on:
|
|
2297
2504
|
- User goals and intent.
|
|
2298
2505
|
- Key decisions made and actions taken.
|
|
2299
2506
|
- Important tool outputs and data retrieved.
|
|
2300
2507
|
- Current state of the task.
|
|
2301
2508
|
|
|
2509
|
+
Previous summary (if any):
|
|
2510
|
+
${previousSummary || "(none)"}
|
|
2511
|
+
|
|
2302
2512
|
Conversation:
|
|
2303
2513
|
${conversationText}
|
|
2304
2514
|
|
|
2305
|
-
Summary
|
|
2306
|
-
|
|
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)
|
|
2307
2521
|
];
|
|
2308
2522
|
let summaryText = "Summary unavailable.";
|
|
2309
2523
|
try {
|
|
@@ -2313,8 +2527,22 @@ Summary:`
|
|
|
2313
2527
|
console.error("[ContextSummarize] Failed to generate summary:", err);
|
|
2314
2528
|
return {};
|
|
2315
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
|
+
});
|
|
2316
2540
|
const newMessages = messages.map((m) => {
|
|
2317
2541
|
if (m.role === "tool") {
|
|
2542
|
+
const toolCallId = m.tool_call_id;
|
|
2543
|
+
if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
|
|
2544
|
+
return null;
|
|
2545
|
+
}
|
|
2318
2546
|
if (m.content === "SUMMARIZED") return m;
|
|
2319
2547
|
return {
|
|
2320
2548
|
...m,
|
|
@@ -2322,7 +2550,8 @@ Summary:`
|
|
|
2322
2550
|
};
|
|
2323
2551
|
}
|
|
2324
2552
|
return m;
|
|
2325
|
-
});
|
|
2553
|
+
}).filter((m) => m !== null);
|
|
2554
|
+
const validatedMessages = validateMessageSequence(newMessages);
|
|
2326
2555
|
const summaryToolCallId = `call_summary_${Date.now()}`;
|
|
2327
2556
|
const assistantSummaryCall = {
|
|
2328
2557
|
role: "assistant",
|
|
@@ -2346,8 +2575,10 @@ Summary:`
|
|
|
2346
2575
|
// Matches the tool name in the call
|
|
2347
2576
|
content: summaryText
|
|
2348
2577
|
};
|
|
2578
|
+
const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
|
|
2349
2579
|
return {
|
|
2350
|
-
messages: [...
|
|
2580
|
+
messages: [...validatedMessages, assistantSummaryCall, toolSummaryResponse],
|
|
2581
|
+
summaries
|
|
2351
2582
|
};
|
|
2352
2583
|
};
|
|
2353
2584
|
}
|
|
@@ -2452,9 +2683,29 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
2452
2683
|
maxTok = 5e4;
|
|
2453
2684
|
}
|
|
2454
2685
|
try {
|
|
2455
|
-
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");
|
|
2456
2696
|
const tokenCount = countApproxTokens(allText);
|
|
2457
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
|
+
}
|
|
2458
2709
|
return needsSum;
|
|
2459
2710
|
} catch (e) {
|
|
2460
2711
|
return false;
|
|
@@ -2512,7 +2763,8 @@ function createSmartAgent(opts) {
|
|
|
2512
2763
|
const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
|
|
2513
2764
|
let state = { ...input, messages: seedMessages };
|
|
2514
2765
|
let lastResult = null;
|
|
2515
|
-
const
|
|
2766
|
+
const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? opts.limits?.maxToolCalls ?? 10;
|
|
2767
|
+
const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
|
|
2516
2768
|
for (let i = 0; i < iterationLimit; i++) {
|
|
2517
2769
|
const next = summarizationEnabled ? decideBefore(state) : "agent";
|
|
2518
2770
|
if (next === "contextSummarize" && summarizer) {
|
|
@@ -2524,7 +2776,11 @@ function createSmartAgent(opts) {
|
|
|
2524
2776
|
}
|
|
2525
2777
|
const res = await base.invoke(state, config);
|
|
2526
2778
|
lastResult = res;
|
|
2779
|
+
const currentSummaries = state.summaries;
|
|
2527
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
|
+
}
|
|
2528
2784
|
if (state.ctx?.__needsSummarization && summarizer) {
|
|
2529
2785
|
const ctx = { ...state.ctx || {} };
|
|
2530
2786
|
delete ctx.__needsSummarization;
|
|
@@ -2552,6 +2808,13 @@ function createSmartAgent(opts) {
|
|
|
2552
2808
|
const res = await base.invoke(state, config);
|
|
2553
2809
|
lastResult = res;
|
|
2554
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
|
+
}
|
|
2555
2818
|
return lastResult;
|
|
2556
2819
|
},
|
|
2557
2820
|
snapshot: base.snapshot,
|