@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.mjs
CHANGED
|
@@ -134,7 +134,7 @@ function withTools(model, tools) {
|
|
|
134
134
|
function fromLangchainModel(lcModel) {
|
|
135
135
|
if (!lcModel) throw new Error("fromLangchainModel: model is undefined/null");
|
|
136
136
|
const adapted = {
|
|
137
|
-
invoke: async (messages) => {
|
|
137
|
+
invoke: async (messages, options) => {
|
|
138
138
|
const normalizeContent = (content) => {
|
|
139
139
|
if (content == null) return content;
|
|
140
140
|
if (typeof content === "string") return content;
|
|
@@ -167,7 +167,7 @@ function fromLangchainModel(lcModel) {
|
|
|
167
167
|
return { role, content: normalizeContent(m.content), name: m.name, tool_calls: m.tool_calls, tool_call_id: m.tool_call_id };
|
|
168
168
|
};
|
|
169
169
|
const lcMessages = messages.map(toLC);
|
|
170
|
-
const response = await lcModel.invoke(lcMessages);
|
|
170
|
+
const response = await lcModel.invoke(lcMessages, options);
|
|
171
171
|
if (response && typeof response === "object") {
|
|
172
172
|
const content = response.content ?? response.text ?? "";
|
|
173
173
|
return {
|
|
@@ -181,6 +181,43 @@ function fromLangchainModel(lcModel) {
|
|
|
181
181
|
}
|
|
182
182
|
return { role: "assistant", content: String(response ?? "") };
|
|
183
183
|
},
|
|
184
|
+
stream: async function* (messages, options) {
|
|
185
|
+
if (typeof lcModel.stream !== "function") return;
|
|
186
|
+
const normalizeContent = (content) => {
|
|
187
|
+
if (content == null) return content;
|
|
188
|
+
if (typeof content === "string") return content;
|
|
189
|
+
if (Array.isArray(content)) {
|
|
190
|
+
return content.map((part) => {
|
|
191
|
+
if (!part || typeof part !== "object") return part;
|
|
192
|
+
if (part.type === "text") return { type: "text", text: String(part.text ?? "") };
|
|
193
|
+
if (part.type === "image_url") {
|
|
194
|
+
const img = part.image_url;
|
|
195
|
+
if (typeof img === "string") return { type: "image_url", image_url: { url: img } };
|
|
196
|
+
if (img && typeof img === "object") {
|
|
197
|
+
if ("url" in img) return { type: "image_url", image_url: img };
|
|
198
|
+
if ("base64" in img) {
|
|
199
|
+
const media = img.media_type || "image/jpeg";
|
|
200
|
+
const dataUrl = `data:${media};base64,${img.base64}`;
|
|
201
|
+
const detail = img.detail;
|
|
202
|
+
return { type: "image_url", image_url: { url: dataUrl, detail } };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return part;
|
|
206
|
+
}
|
|
207
|
+
return part;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return content;
|
|
211
|
+
};
|
|
212
|
+
const toLC = (m) => {
|
|
213
|
+
if (m._getType || m.lc_serializable) return m;
|
|
214
|
+
return { role: m.role, content: normalizeContent(m.content), name: m.name, tool_calls: m.tool_calls, tool_call_id: m.tool_call_id };
|
|
215
|
+
};
|
|
216
|
+
const lcMessages = messages.map(toLC);
|
|
217
|
+
for await (const chunk of lcModel.stream(lcMessages, options)) {
|
|
218
|
+
yield chunk;
|
|
219
|
+
}
|
|
220
|
+
},
|
|
184
221
|
bindTools: (tools) => {
|
|
185
222
|
const lcReady = toLangchainTools(tools);
|
|
186
223
|
if (typeof lcModel.bindTools === "function") {
|
|
@@ -334,7 +371,7 @@ function safe(v) {
|
|
|
334
371
|
const n = Number(v);
|
|
335
372
|
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
336
373
|
}
|
|
337
|
-
var DEFAULT_COGNIPEER_URL = "https://
|
|
374
|
+
var DEFAULT_COGNIPEER_URL = "https://console.cognipeer.com/api/client/v1/tracing/sessions";
|
|
338
375
|
function fileSink(path2) {
|
|
339
376
|
return { type: "file", path: path2 };
|
|
340
377
|
}
|
|
@@ -392,133 +429,113 @@ function coerceToString(value) {
|
|
|
392
429
|
}
|
|
393
430
|
return void 0;
|
|
394
431
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
432
|
+
var METADATA_PATHS = {
|
|
433
|
+
model: [
|
|
434
|
+
"model",
|
|
435
|
+
"options.model",
|
|
436
|
+
"params.model",
|
|
437
|
+
"config.model",
|
|
438
|
+
"configuration.model",
|
|
439
|
+
"metadata.model",
|
|
440
|
+
"lc_kwargs.model",
|
|
441
|
+
"metadata.modelName",
|
|
442
|
+
"modelName",
|
|
443
|
+
"lc_alias",
|
|
444
|
+
"model_id",
|
|
445
|
+
"id",
|
|
446
|
+
"_model",
|
|
447
|
+
"_modelName",
|
|
448
|
+
"__model",
|
|
449
|
+
"_modelId",
|
|
450
|
+
"_llmType"
|
|
451
|
+
],
|
|
452
|
+
provider: [
|
|
453
|
+
"provider",
|
|
454
|
+
"options.provider",
|
|
455
|
+
"params.provider",
|
|
456
|
+
"config.provider",
|
|
457
|
+
"configuration.provider",
|
|
458
|
+
"metadata.provider",
|
|
459
|
+
"lc_kwargs.provider"
|
|
460
|
+
]
|
|
461
|
+
};
|
|
462
|
+
function getNestedValue(obj, path2) {
|
|
463
|
+
if (!obj || typeof path2 !== "string") return void 0;
|
|
464
|
+
return path2.split(".").reduce((current, key) => current?.[key], obj);
|
|
401
465
|
}
|
|
402
|
-
function
|
|
466
|
+
function extractMetadata(model, paths) {
|
|
403
467
|
if (!model) return void 0;
|
|
468
|
+
for (const path2 of paths) {
|
|
469
|
+
const value = getNestedValue(model, path2);
|
|
470
|
+
const str = coerceToString(value);
|
|
471
|
+
if (str) return str;
|
|
472
|
+
}
|
|
404
473
|
const maybeLc = model?._lc;
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
model?.lc_kwargs?.model,
|
|
413
|
-
maybeLc?.model,
|
|
414
|
-
maybeLc?.options?.model,
|
|
415
|
-
maybeLc?.params?.model,
|
|
416
|
-
maybeLc?.config?.model,
|
|
417
|
-
maybeLc?.configuration?.model,
|
|
418
|
-
maybeLc?.metadata?.model,
|
|
419
|
-
maybeLc?.lc_kwargs?.model,
|
|
420
|
-
model?.metadata?.modelName,
|
|
421
|
-
maybeLc?.metadata?.modelName,
|
|
422
|
-
model?.modelName,
|
|
423
|
-
maybeLc?.modelName,
|
|
424
|
-
model?.lc_alias,
|
|
425
|
-
model?.model_id,
|
|
426
|
-
model?.id,
|
|
427
|
-
maybeLc?.model_id,
|
|
428
|
-
maybeLc?.id,
|
|
429
|
-
model?._model,
|
|
430
|
-
model?._modelName,
|
|
431
|
-
model?.__model
|
|
432
|
-
);
|
|
433
|
-
if (direct) return direct;
|
|
474
|
+
if (maybeLc) {
|
|
475
|
+
for (const path2 of paths) {
|
|
476
|
+
const value = getNestedValue(maybeLc, path2);
|
|
477
|
+
const str = coerceToString(value);
|
|
478
|
+
if (str) return str;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
434
481
|
const maybeClient = model?.client || model?.api || model?.service;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
482
|
+
if (maybeClient) {
|
|
483
|
+
for (const path2 of paths) {
|
|
484
|
+
const value = getNestedValue(maybeClient, path2);
|
|
485
|
+
const str = coerceToString(value);
|
|
486
|
+
if (str) return str;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const constructorName = model?.constructor?.name;
|
|
490
|
+
if (constructorName && !["Object", "Function"].includes(constructorName)) {
|
|
491
|
+
return constructorName;
|
|
492
|
+
}
|
|
493
|
+
return void 0;
|
|
494
|
+
}
|
|
495
|
+
function getModelName(model) {
|
|
496
|
+
return extractMetadata(model, METADATA_PATHS.model);
|
|
444
497
|
}
|
|
445
498
|
function getProviderName(model) {
|
|
446
|
-
|
|
447
|
-
const direct = firstNonEmptyString(
|
|
448
|
-
model?.provider,
|
|
449
|
-
model?.options?.provider,
|
|
450
|
-
model?.params?.provider,
|
|
451
|
-
model?.config?.provider,
|
|
452
|
-
model?.configuration?.provider,
|
|
453
|
-
model?.metadata?.provider,
|
|
454
|
-
model?.lc_kwargs?.provider,
|
|
455
|
-
model?.lc_alias,
|
|
456
|
-
model?.__provider
|
|
457
|
-
);
|
|
458
|
-
if (direct) return direct;
|
|
459
|
-
const maybeLc = model?._lc;
|
|
460
|
-
const lcValue = maybeLc ? firstNonEmptyString(
|
|
461
|
-
maybeLc?.provider,
|
|
462
|
-
maybeLc?.options?.provider,
|
|
463
|
-
maybeLc?.config?.provider,
|
|
464
|
-
maybeLc?.configuration?.provider,
|
|
465
|
-
maybeLc?.metadata?.provider,
|
|
466
|
-
maybeLc?.lc_kwargs?.provider,
|
|
467
|
-
maybeLc?.client?.config?.provider
|
|
468
|
-
) : void 0;
|
|
469
|
-
if (lcValue) return lcValue;
|
|
470
|
-
const maybeClient = model?.client || model?.api || model?.service;
|
|
471
|
-
return firstNonEmptyString(
|
|
472
|
-
maybeClient?.provider,
|
|
473
|
-
maybeClient?.config?.provider,
|
|
474
|
-
maybeClient?.options?.provider,
|
|
475
|
-
maybeClient?.metadata?.provider
|
|
476
|
-
);
|
|
499
|
+
return extractMetadata(model, METADATA_PATHS.provider);
|
|
477
500
|
}
|
|
478
501
|
function inferModelFromMessages(messageList) {
|
|
479
502
|
if (!Array.isArray(messageList)) return void 0;
|
|
480
|
-
|
|
503
|
+
const messagePaths = [
|
|
504
|
+
"metadata.model",
|
|
505
|
+
"metadata.modelName",
|
|
506
|
+
"response_metadata.model",
|
|
507
|
+
"response_metadata.modelName",
|
|
508
|
+
"usage_metadata.model",
|
|
509
|
+
"model",
|
|
510
|
+
"modelName"
|
|
511
|
+
];
|
|
512
|
+
for (let i = messageList.length - 1; i >= 0; i--) {
|
|
481
513
|
const message = messageList[i];
|
|
482
514
|
if (!message) continue;
|
|
483
|
-
const
|
|
484
|
-
message
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
message?.response_metadata?.model,
|
|
489
|
-
message?.response_metadata?.modelName,
|
|
490
|
-
message?.response_metadata?.modelNames,
|
|
491
|
-
message?.response_metadata?.model_id,
|
|
492
|
-
message?.usage_metadata?.model,
|
|
493
|
-
message?.model,
|
|
494
|
-
message?.modelName,
|
|
495
|
-
message?.model_id,
|
|
496
|
-
message?.additional_kwargs?.model,
|
|
497
|
-
message?.additional_kwargs?.modelName,
|
|
498
|
-
message?.info?.model,
|
|
499
|
-
message?.annotations?.model
|
|
500
|
-
);
|
|
501
|
-
if (candidate) return candidate;
|
|
515
|
+
for (const path2 of messagePaths) {
|
|
516
|
+
const value = getNestedValue(message, path2);
|
|
517
|
+
const str = coerceToString(value);
|
|
518
|
+
if (str) return str;
|
|
519
|
+
}
|
|
502
520
|
}
|
|
503
521
|
return void 0;
|
|
504
522
|
}
|
|
505
523
|
function inferProviderFromMessages(messageList) {
|
|
506
524
|
if (!Array.isArray(messageList)) return void 0;
|
|
507
|
-
|
|
525
|
+
const messagePaths = [
|
|
526
|
+
"metadata.provider",
|
|
527
|
+
"response_metadata.provider",
|
|
528
|
+
"usage_metadata.provider",
|
|
529
|
+
"provider"
|
|
530
|
+
];
|
|
531
|
+
for (let i = messageList.length - 1; i >= 0; i--) {
|
|
508
532
|
const message = messageList[i];
|
|
509
533
|
if (!message) continue;
|
|
510
|
-
const
|
|
511
|
-
message
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
message?.usage_metadata?.provider,
|
|
516
|
-
message?.provider,
|
|
517
|
-
message?.additional_kwargs?.provider,
|
|
518
|
-
message?.info?.provider,
|
|
519
|
-
message?.annotations?.provider
|
|
520
|
-
);
|
|
521
|
-
if (candidate) return candidate;
|
|
534
|
+
for (const path2 of messagePaths) {
|
|
535
|
+
const value = getNestedValue(message, path2);
|
|
536
|
+
const str = coerceToString(value);
|
|
537
|
+
if (str) return str;
|
|
538
|
+
}
|
|
522
539
|
}
|
|
523
540
|
return void 0;
|
|
524
541
|
}
|
|
@@ -616,20 +633,32 @@ async function postTraceSession(url, headers, payload) {
|
|
|
616
633
|
if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
|
|
617
634
|
finalHeaders["content-type"] = "application/json";
|
|
618
635
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
636
|
+
try {
|
|
637
|
+
const response = await fetchFn(url, {
|
|
638
|
+
method: "POST",
|
|
639
|
+
headers: finalHeaders,
|
|
640
|
+
body: JSON.stringify(payload)
|
|
641
|
+
});
|
|
642
|
+
if (!response.ok) {
|
|
643
|
+
let responseText = "";
|
|
644
|
+
try {
|
|
645
|
+
responseText = await response.text();
|
|
646
|
+
} catch {
|
|
647
|
+
}
|
|
648
|
+
const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
|
|
649
|
+
const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
|
|
650
|
+
const errorMsg = `${statusLine}${bodyPreview}`;
|
|
651
|
+
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
652
|
+
console.error("[Tracing] Error posting to", url, ":", errorMsg);
|
|
653
|
+
}
|
|
654
|
+
throw new Error(errorMsg);
|
|
629
655
|
}
|
|
630
|
-
|
|
631
|
-
const
|
|
632
|
-
|
|
656
|
+
} catch (err) {
|
|
657
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
658
|
+
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
659
|
+
console.error("[Tracing] Failed to post:", errMsg);
|
|
660
|
+
}
|
|
661
|
+
throw err;
|
|
633
662
|
}
|
|
634
663
|
}
|
|
635
664
|
function createEmptySummary() {
|
|
@@ -930,12 +959,16 @@ async function finalizeTraceSession(session, params = {}) {
|
|
|
930
959
|
const endedAtIso = new Date(endedAtMs).toISOString();
|
|
931
960
|
const durationMs = endedAtMs - session.startedAt;
|
|
932
961
|
const startedAtIso = new Date(session.startedAt).toISOString();
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
962
|
+
const buildAgentInfo = (runtime) => {
|
|
963
|
+
if (!runtime) return void 0;
|
|
964
|
+
return {
|
|
965
|
+
name: runtime.name || "unknown-agent",
|
|
966
|
+
version: runtime.version,
|
|
967
|
+
model: getModelName(runtime.model) || "unknown-model",
|
|
968
|
+
provider: getProviderName(runtime.model)
|
|
969
|
+
};
|
|
970
|
+
};
|
|
971
|
+
const agentInfo = buildAgentInfo(params.agentRuntime);
|
|
939
972
|
if (params.error) {
|
|
940
973
|
session.errors.push({
|
|
941
974
|
eventId: "session",
|
|
@@ -1103,12 +1136,29 @@ function createAgentCoreNode(opts) {
|
|
|
1103
1136
|
};
|
|
1104
1137
|
const normalizeBedrockToolPairing = (input) => {
|
|
1105
1138
|
const msgs = Array.isArray(input) ? [...input] : [];
|
|
1106
|
-
|
|
1107
|
-
|
|
1139
|
+
const validToolCallIds = /* @__PURE__ */ new Set();
|
|
1140
|
+
for (const m of msgs) {
|
|
1141
|
+
if (m?.role === "assistant") {
|
|
1142
|
+
for (const tu of extractToolUses(m)) {
|
|
1143
|
+
if (tu.id) validToolCallIds.add(tu.id);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
const filteredMsgs = msgs.filter((m) => {
|
|
1148
|
+
if (m?.role === "tool") {
|
|
1149
|
+
const toolCallId = m.tool_call_id;
|
|
1150
|
+
if (!toolCallId || !validToolCallIds.has(toolCallId)) {
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return true;
|
|
1155
|
+
});
|
|
1156
|
+
for (let i = 0; i < filteredMsgs.length; i++) {
|
|
1157
|
+
const m = filteredMsgs[i];
|
|
1108
1158
|
if (m?.role !== "assistant") continue;
|
|
1109
1159
|
const toolUses = extractToolUses(m);
|
|
1110
1160
|
if (toolUses.length === 0) continue;
|
|
1111
|
-
const next =
|
|
1161
|
+
const next = filteredMsgs[i + 1];
|
|
1112
1162
|
const firstId = toolUses[0]?.id;
|
|
1113
1163
|
const nextIsToolForFirst = !!(next && next.role === "tool" && next.tool_call_id === firstId);
|
|
1114
1164
|
if (nextIsToolForFirst) continue;
|
|
@@ -1116,12 +1166,12 @@ function createAgentCoreNode(opts) {
|
|
|
1116
1166
|
role: "tool",
|
|
1117
1167
|
name: tu.name || "unknown_tool",
|
|
1118
1168
|
tool_call_id: tu.id,
|
|
1119
|
-
content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for
|
|
1169
|
+
content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for tool_result adjacency."
|
|
1120
1170
|
}));
|
|
1121
|
-
|
|
1171
|
+
filteredMsgs.splice(i + 1, 0, ...placeholders);
|
|
1122
1172
|
i += placeholders.length;
|
|
1123
1173
|
}
|
|
1124
|
-
return
|
|
1174
|
+
return filteredMsgs;
|
|
1125
1175
|
};
|
|
1126
1176
|
const debugToolPairing = (messagesToSend) => {
|
|
1127
1177
|
try {
|
|
@@ -1143,11 +1193,54 @@ function createAgentCoreNode(opts) {
|
|
|
1143
1193
|
} catch (e) {
|
|
1144
1194
|
}
|
|
1145
1195
|
};
|
|
1196
|
+
const onEvent = state.ctx?.__onEvent;
|
|
1197
|
+
const onStream = state.ctx?.__onStream;
|
|
1198
|
+
const streamingEnabled = Boolean(state.ctx?.__streaming);
|
|
1199
|
+
const cancellationToken = state.ctx?.__cancellationToken;
|
|
1200
|
+
const abortSignal = state.ctx?.__abortSignal;
|
|
1201
|
+
const extractText = (chunk) => {
|
|
1202
|
+
if (chunk == null) return "";
|
|
1203
|
+
if (typeof chunk === "string") return chunk;
|
|
1204
|
+
if (typeof chunk?.content === "string") return chunk.content;
|
|
1205
|
+
if (Array.isArray(chunk?.content)) {
|
|
1206
|
+
return chunk.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
|
|
1207
|
+
}
|
|
1208
|
+
if (typeof chunk?.text === "string") return chunk.text;
|
|
1209
|
+
if (typeof chunk?.delta?.content === "string") return chunk.delta.content;
|
|
1210
|
+
return "";
|
|
1211
|
+
};
|
|
1146
1212
|
let response;
|
|
1147
1213
|
try {
|
|
1148
1214
|
const normalizedMessages = normalizeBedrockToolPairing([...state.messages]);
|
|
1149
1215
|
debugToolPairing(normalizedMessages);
|
|
1150
|
-
|
|
1216
|
+
if (streamingEnabled && typeof modelWithTools.stream === "function") {
|
|
1217
|
+
let streamedText = "";
|
|
1218
|
+
let streamedMessage;
|
|
1219
|
+
for await (const chunk of modelWithTools.stream(normalizedMessages, { signal: abortSignal, cancellationToken })) {
|
|
1220
|
+
if (cancellationToken && cancellationToken.isCancellationRequested || abortSignal?.aborted) {
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
if (chunk && typeof chunk === "object" && chunk.role) {
|
|
1224
|
+
streamedMessage = chunk;
|
|
1225
|
+
}
|
|
1226
|
+
const text = extractText(chunk);
|
|
1227
|
+
if (text) {
|
|
1228
|
+
streamedText += text;
|
|
1229
|
+
onStream?.({ text });
|
|
1230
|
+
onEvent?.({ type: "stream", text });
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (streamedMessage) {
|
|
1234
|
+
response = { ...streamedMessage };
|
|
1235
|
+
if (response.content == null || response.content === "") {
|
|
1236
|
+
response.content = streamedText;
|
|
1237
|
+
}
|
|
1238
|
+
} else {
|
|
1239
|
+
response = { role: "assistant", content: streamedText };
|
|
1240
|
+
}
|
|
1241
|
+
} else {
|
|
1242
|
+
response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken });
|
|
1243
|
+
}
|
|
1151
1244
|
} catch (err) {
|
|
1152
1245
|
const durationMs2 = Date.now() - start;
|
|
1153
1246
|
recordTraceEvent(traceSession, {
|
|
@@ -1168,7 +1261,7 @@ function createAgentCoreNode(opts) {
|
|
|
1168
1261
|
...state.messages,
|
|
1169
1262
|
response
|
|
1170
1263
|
];
|
|
1171
|
-
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.usage;
|
|
1264
|
+
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
|
|
1172
1265
|
const normalized = normalizeUsage(rawUsage);
|
|
1173
1266
|
const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
|
|
1174
1267
|
const durationMs = Date.now() - start;
|
|
@@ -1235,6 +1328,9 @@ function createToolsNode(initialTools, opts) {
|
|
|
1235
1328
|
};
|
|
1236
1329
|
const appended = [];
|
|
1237
1330
|
const onEvent = state.ctx?.__onEvent;
|
|
1331
|
+
const onProgress = state.ctx?.__onProgress;
|
|
1332
|
+
const cancellationToken = state.ctx?.__cancellationToken;
|
|
1333
|
+
const abortSignal = state.ctx?.__abortSignal;
|
|
1238
1334
|
const traceSession = state.ctx?.__traceSession;
|
|
1239
1335
|
const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
|
|
1240
1336
|
const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
|
|
@@ -1291,7 +1387,20 @@ function createToolsNode(initialTools, opts) {
|
|
|
1291
1387
|
name: tc.name
|
|
1292
1388
|
});
|
|
1293
1389
|
}
|
|
1390
|
+
const isCancelled = () => {
|
|
1391
|
+
if (abortSignal?.aborted) return { cancelled: true, reason: "aborted" };
|
|
1392
|
+
if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
|
|
1393
|
+
return { cancelled: false, reason: void 0 };
|
|
1394
|
+
};
|
|
1294
1395
|
const runOne = async (tc) => {
|
|
1396
|
+
const cancelState = isCancelled();
|
|
1397
|
+
if (cancelState.cancelled) {
|
|
1398
|
+
const ctx = state.ctx = state.ctx || {};
|
|
1399
|
+
ctx.__cancelled = { stage: "tools", reason: cancelState.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1400
|
+
onEvent?.({ type: "cancelled", stage: "tools", reason: cancelState.reason });
|
|
1401
|
+
onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
|
|
1402
|
+
return { status: "error" };
|
|
1403
|
+
}
|
|
1295
1404
|
const t = toolByName.get(tc.name);
|
|
1296
1405
|
if (!t) {
|
|
1297
1406
|
onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
|
|
@@ -1370,15 +1479,17 @@ function createToolsNode(initialTools, opts) {
|
|
|
1370
1479
|
const start = Date.now();
|
|
1371
1480
|
try {
|
|
1372
1481
|
onEvent?.({ type: "tool_call", phase: "start", name: t.name, id: tc.id, args });
|
|
1482
|
+
onProgress?.({ stage: "tools", message: `Running tool ${tc.name}` });
|
|
1373
1483
|
const sanitizedArgs = sanitizeTracePayload(args);
|
|
1374
1484
|
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1375
1485
|
let output;
|
|
1376
1486
|
const anyTool = t;
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
else if (typeof anyTool.
|
|
1380
|
-
else if (typeof anyTool.
|
|
1381
|
-
else if (typeof anyTool.
|
|
1487
|
+
const callOptions = { cancellationToken, signal: abortSignal };
|
|
1488
|
+
if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
|
|
1489
|
+
else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
|
|
1490
|
+
else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
|
|
1491
|
+
else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
|
|
1492
|
+
else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
|
|
1382
1493
|
else throw new Error("Tool is not invokable");
|
|
1383
1494
|
if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
|
|
1384
1495
|
const newRuntime = output.__handoff.runtime;
|
|
@@ -1408,6 +1519,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1408
1519
|
toolHistory.push({ executionId, toolName: t.name, args, output, rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
|
|
1409
1520
|
appended.push({ role: "tool", content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1410
1521
|
onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: output, durationMs });
|
|
1522
|
+
onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
|
|
1411
1523
|
if (needsApproval && approvalEntry) {
|
|
1412
1524
|
approvalEntry.status = "executed";
|
|
1413
1525
|
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1457,6 +1569,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1457
1569
|
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 });
|
|
1458
1570
|
appended.push({ role: "tool", content: `Error executing tool: ${e?.message || String(e)}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1459
1571
|
onEvent?.({ type: "tool_call", phase: "error", name: t.name, id: tc.id, args, error: { message: e?.message || String(e) } });
|
|
1572
|
+
onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: e?.message || String(e) } });
|
|
1460
1573
|
const sanitizedArgs = sanitizeTracePayload(args);
|
|
1461
1574
|
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1462
1575
|
const toolName = t.name || tc.name;
|
|
@@ -1500,6 +1613,14 @@ function createToolsNode(initialTools, opts) {
|
|
|
1500
1613
|
};
|
|
1501
1614
|
for (const tc of planned) {
|
|
1502
1615
|
if (awaitingApproval) break;
|
|
1616
|
+
const cancelState = isCancelled();
|
|
1617
|
+
if (cancelState.cancelled) {
|
|
1618
|
+
const ctx = state.ctx = state.ctx || {};
|
|
1619
|
+
ctx.__cancelled = { stage: "tools", reason: cancelState.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1620
|
+
onEvent?.({ type: "cancelled", stage: "tools", reason: cancelState.reason });
|
|
1621
|
+
onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
|
|
1622
|
+
break;
|
|
1623
|
+
}
|
|
1503
1624
|
const result = await runOne(tc);
|
|
1504
1625
|
if (result?.status === "awaiting_approval") {
|
|
1505
1626
|
awaitingApproval = true;
|
|
@@ -1527,7 +1648,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1527
1648
|
// src/nodes/toolLimitFinalize.ts
|
|
1528
1649
|
function createToolLimitFinalizeNode(_opts) {
|
|
1529
1650
|
return async (state) => {
|
|
1530
|
-
const notice = { role: "
|
|
1651
|
+
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." };
|
|
1531
1652
|
const ctx = { ...state.ctx || {}, __finalizedDueToToolLimit: true };
|
|
1532
1653
|
return { messages: [...state.messages, notice], ctx };
|
|
1533
1654
|
};
|
|
@@ -1804,12 +1925,37 @@ function createAgent(opts) {
|
|
|
1804
1925
|
delete nextCtx.__resumeStage;
|
|
1805
1926
|
state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
|
|
1806
1927
|
}
|
|
1807
|
-
const
|
|
1928
|
+
const mergedLimits = {
|
|
1929
|
+
...opts.limits || {},
|
|
1930
|
+
...config?.limits || {}
|
|
1931
|
+
};
|
|
1932
|
+
const maxToolCalls = mergedLimits?.maxToolCalls === void 0 ? 50 : mergedLimits?.maxToolCalls;
|
|
1808
1933
|
const iterationLimit = maxToolCalls === Infinity ? 100 : Math.max(maxToolCalls * 3 + 10, 40);
|
|
1809
1934
|
let iterations = 0;
|
|
1810
1935
|
const onStateChange = config?.onStateChange;
|
|
1811
1936
|
const checkpointReason = config?.checkpointReason;
|
|
1812
1937
|
let pausedStage = null;
|
|
1938
|
+
const onProgress = state.ctx?.__onProgress;
|
|
1939
|
+
const isCancelled = () => {
|
|
1940
|
+
const ctx = state.ctx || {};
|
|
1941
|
+
const token = ctx.__cancellationToken;
|
|
1942
|
+
const signal = ctx.__abortSignal;
|
|
1943
|
+
const deadline = ctx.__deadline;
|
|
1944
|
+
if (signal?.aborted) return { cancelled: true, reason: "aborted" };
|
|
1945
|
+
if (token && token.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
|
|
1946
|
+
if (deadline && Date.now() > deadline) return { cancelled: true, reason: "timeout" };
|
|
1947
|
+
return { cancelled: false, reason: void 0 };
|
|
1948
|
+
};
|
|
1949
|
+
const cancelIfRequested = (stage) => {
|
|
1950
|
+
const result = isCancelled();
|
|
1951
|
+
if (!result.cancelled) return false;
|
|
1952
|
+
const ctx = { ...state.ctx || {} };
|
|
1953
|
+
ctx.__cancelled = { stage, reason: result.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1954
|
+
state = { ...state, ctx };
|
|
1955
|
+
emit?.({ type: "cancelled", stage, reason: result.reason });
|
|
1956
|
+
onProgress?.({ stage, message: "Cancelled", detail: { reason: result.reason } });
|
|
1957
|
+
return true;
|
|
1958
|
+
};
|
|
1813
1959
|
const checkpointIfRequested = (stage) => {
|
|
1814
1960
|
if (typeof onStateChange !== "function") return false;
|
|
1815
1961
|
let result = false;
|
|
@@ -1832,8 +1978,10 @@ function createAgent(opts) {
|
|
|
1832
1978
|
};
|
|
1833
1979
|
while (iterations < iterationLimit) {
|
|
1834
1980
|
iterations++;
|
|
1981
|
+
if (cancelIfRequested("loop")) break;
|
|
1835
1982
|
const skippingAgent = resumeStage === "tools";
|
|
1836
1983
|
if (!skippingAgent) {
|
|
1984
|
+
if (cancelIfRequested("before_guardrails")) break;
|
|
1837
1985
|
if (checkpointIfRequested("before_guardrails")) break;
|
|
1838
1986
|
const preGuardrails = getGuardrailConfig(state);
|
|
1839
1987
|
if (preGuardrails.length > 0) {
|
|
@@ -1888,7 +2036,10 @@ function createAgent(opts) {
|
|
|
1888
2036
|
break;
|
|
1889
2037
|
}
|
|
1890
2038
|
}
|
|
2039
|
+
onProgress?.({ stage: "agent", message: "Invoking model" });
|
|
2040
|
+
if (cancelIfRequested("before_agent")) break;
|
|
1891
2041
|
state = { ...state, ...await agentCore(state) };
|
|
2042
|
+
onProgress?.({ stage: "agent", message: "Model response received" });
|
|
1892
2043
|
if (checkpointIfRequested("after_agent")) break;
|
|
1893
2044
|
const postGuardrails = getGuardrailConfig(state);
|
|
1894
2045
|
if (postGuardrails.length > 0) {
|
|
@@ -1956,7 +2107,10 @@ function createAgent(opts) {
|
|
|
1956
2107
|
continue;
|
|
1957
2108
|
}
|
|
1958
2109
|
if (toolCalls.length === 0) break;
|
|
2110
|
+
onProgress?.({ stage: "tools", message: "Running tools" });
|
|
2111
|
+
if (cancelIfRequested("before_tools")) break;
|
|
1959
2112
|
state = { ...state, ...await toolsNode(state) };
|
|
2113
|
+
onProgress?.({ stage: "tools", message: "Tools finished" });
|
|
1960
2114
|
if (state.ctx?.__awaitingApproval) break;
|
|
1961
2115
|
if (checkpointIfRequested("after_tools")) break;
|
|
1962
2116
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
@@ -1997,15 +2151,39 @@ function createAgent(opts) {
|
|
|
1997
2151
|
}
|
|
1998
2152
|
const invokeAgent = async (input, config) => {
|
|
1999
2153
|
const onEvent = config?.onEvent;
|
|
2154
|
+
const onProgress = config?.onProgress;
|
|
2155
|
+
const onStream = config?.onStream;
|
|
2156
|
+
const streamEnabled = config?.stream === true;
|
|
2000
2157
|
const emit = (e) => {
|
|
2001
2158
|
try {
|
|
2002
2159
|
onEvent?.(e);
|
|
2003
2160
|
} catch {
|
|
2004
2161
|
}
|
|
2005
2162
|
};
|
|
2163
|
+
const emitProgress = (progress) => {
|
|
2164
|
+
try {
|
|
2165
|
+
onProgress?.(progress);
|
|
2166
|
+
} catch {
|
|
2167
|
+
}
|
|
2168
|
+
emit({ type: "progress", ...progress });
|
|
2169
|
+
};
|
|
2006
2170
|
const traceSession = createTraceSession(opts);
|
|
2007
|
-
const ctx = {
|
|
2171
|
+
const ctx = {
|
|
2172
|
+
...input.ctx || {},
|
|
2173
|
+
__onEvent: onEvent,
|
|
2174
|
+
__onProgress: emitProgress,
|
|
2175
|
+
__onStream: onStream,
|
|
2176
|
+
__streaming: streamEnabled
|
|
2177
|
+
};
|
|
2008
2178
|
if (traceSession) ctx.__traceSession = traceSession;
|
|
2179
|
+
if (config?.cancellationToken) ctx.__cancellationToken = config.cancellationToken;
|
|
2180
|
+
if (config?.cancellationToken?.aborted !== void 0) {
|
|
2181
|
+
ctx.__abortSignal = config?.cancellationToken;
|
|
2182
|
+
}
|
|
2183
|
+
if (config?.timeoutMs && config.timeoutMs > 0) {
|
|
2184
|
+
ctx.__deadline = Date.now() + config.timeoutMs;
|
|
2185
|
+
}
|
|
2186
|
+
const runtimeWithInvokeLimits = config?.limits ? { ...runtime, limits: { ...runtime.limits || {}, ...config.limits } } : runtime;
|
|
2009
2187
|
const initial = {
|
|
2010
2188
|
messages: input.messages || [],
|
|
2011
2189
|
toolCallCount: input.toolCallCount || 0,
|
|
@@ -2014,7 +2192,7 @@ function createAgent(opts) {
|
|
|
2014
2192
|
metadata: input.metadata,
|
|
2015
2193
|
ctx,
|
|
2016
2194
|
pendingApprovals: input.pendingApprovals || [],
|
|
2017
|
-
agent: input.agent ||
|
|
2195
|
+
agent: input.agent || runtimeWithInvokeLimits,
|
|
2018
2196
|
usage: input.usage || { perRequest: [], totals: {} }
|
|
2019
2197
|
};
|
|
2020
2198
|
let res;
|
|
@@ -2057,6 +2235,10 @@ function createAgent(opts) {
|
|
|
2057
2235
|
}
|
|
2058
2236
|
}
|
|
2059
2237
|
emit({ type: "finalAnswer", content: typeof finalMsg?.content === "string" ? finalMsg.content : content });
|
|
2238
|
+
if (streamEnabled && content) {
|
|
2239
|
+
onStream?.({ text: content, isFinal: true });
|
|
2240
|
+
emit({ type: "stream", text: content, isFinal: true });
|
|
2241
|
+
}
|
|
2060
2242
|
return {
|
|
2061
2243
|
content,
|
|
2062
2244
|
output: parsed,
|
|
@@ -2227,16 +2409,47 @@ function createAgentNode(opts) {
|
|
|
2227
2409
|
// src/nodes/contextSummarize.ts
|
|
2228
2410
|
var systemMessage = (content) => ({ role: "system", content });
|
|
2229
2411
|
var humanMessage = (content) => ({ role: "user", content });
|
|
2412
|
+
function validateMessageSequence(messages) {
|
|
2413
|
+
const validated = [];
|
|
2414
|
+
const pendingToolCallIds = /* @__PURE__ */ new Set();
|
|
2415
|
+
for (const m of messages) {
|
|
2416
|
+
if (m.role === "assistant") {
|
|
2417
|
+
if (!m.tool_calls || m.tool_calls.length === 0) {
|
|
2418
|
+
pendingToolCallIds.clear();
|
|
2419
|
+
} else {
|
|
2420
|
+
m.tool_calls.forEach((tc) => {
|
|
2421
|
+
if (tc.id) pendingToolCallIds.add(tc.id);
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
validated.push(m);
|
|
2425
|
+
} else if (m.role === "tool") {
|
|
2426
|
+
const toolCallId = m.tool_call_id;
|
|
2427
|
+
if (toolCallId && pendingToolCallIds.has(toolCallId)) {
|
|
2428
|
+
pendingToolCallIds.delete(toolCallId);
|
|
2429
|
+
validated.push(m);
|
|
2430
|
+
}
|
|
2431
|
+
} else {
|
|
2432
|
+
validated.push(m);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return validated;
|
|
2436
|
+
}
|
|
2230
2437
|
function getSummarizationConfig(opts) {
|
|
2231
2438
|
if (typeof opts.summarization === "object") {
|
|
2232
2439
|
return {
|
|
2233
2440
|
enabled: opts.summarization.enable !== false,
|
|
2234
|
-
maxTokens: opts.summarization.maxTokens
|
|
2441
|
+
maxTokens: opts.summarization.maxTokens ?? 5e4,
|
|
2442
|
+
// Max tokens we can send to the model for summarization prompt itself
|
|
2443
|
+
// This should be well under the model's context limit
|
|
2444
|
+
summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3,
|
|
2445
|
+
promptTemplate: opts.summarization.promptTemplate
|
|
2235
2446
|
};
|
|
2236
2447
|
}
|
|
2237
2448
|
return {
|
|
2238
2449
|
enabled: opts.summarization !== false,
|
|
2239
|
-
maxTokens: 5e4
|
|
2450
|
+
maxTokens: 5e4,
|
|
2451
|
+
summaryPromptMaxTokens: 8e3,
|
|
2452
|
+
promptTemplate: void 0
|
|
2240
2453
|
};
|
|
2241
2454
|
}
|
|
2242
2455
|
function createContextSummarizeNode(opts) {
|
|
@@ -2249,31 +2462,53 @@ function createContextSummarizeNode(opts) {
|
|
|
2249
2462
|
if (compressableMessages.length === 0) {
|
|
2250
2463
|
return {};
|
|
2251
2464
|
}
|
|
2252
|
-
const
|
|
2465
|
+
const summaryPromptMaxTokens = config.summaryPromptMaxTokens;
|
|
2466
|
+
const availableTokensForConversation = summaryPromptMaxTokens - 500;
|
|
2467
|
+
const messageTexts = [];
|
|
2468
|
+
let accumulatedTokens = 0;
|
|
2469
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2470
|
+
const m = messages[i];
|
|
2253
2471
|
let content = "";
|
|
2254
2472
|
if (typeof m.content === "string") content = m.content;
|
|
2255
2473
|
else if (Array.isArray(m.content)) {
|
|
2256
2474
|
content = m.content.map((c) => c.text || JSON.stringify(c)).join(" ");
|
|
2257
2475
|
}
|
|
2476
|
+
const maxContentLength = 2e3;
|
|
2477
|
+
if (content.length > maxContentLength) {
|
|
2478
|
+
content = content.substring(0, maxContentLength) + "... [TRUNCATED]";
|
|
2479
|
+
}
|
|
2258
2480
|
let prefix = `${m.role.toUpperCase()}`;
|
|
2259
2481
|
if (m.name) prefix += ` (${m.name})`;
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2482
|
+
const messageText = `${prefix}: ${content}`;
|
|
2483
|
+
const messageTokens = countApproxTokens(messageText);
|
|
2484
|
+
if (accumulatedTokens + messageTokens > availableTokensForConversation) {
|
|
2485
|
+
messageTexts.unshift("[Earlier conversation history omitted for brevity]");
|
|
2486
|
+
break;
|
|
2487
|
+
}
|
|
2488
|
+
messageTexts.unshift(messageText);
|
|
2489
|
+
accumulatedTokens += messageTokens;
|
|
2490
|
+
}
|
|
2491
|
+
const conversationText = messageTexts.join("\n\n");
|
|
2492
|
+
const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
|
|
2493
|
+
const defaultPrompt = `Please summarize the following conversation history and update any previous summary.
|
|
2266
2494
|
Focus on:
|
|
2267
2495
|
- User goals and intent.
|
|
2268
2496
|
- Key decisions made and actions taken.
|
|
2269
2497
|
- Important tool outputs and data retrieved.
|
|
2270
2498
|
- Current state of the task.
|
|
2271
2499
|
|
|
2500
|
+
Previous summary (if any):
|
|
2501
|
+
${previousSummary || "(none)"}
|
|
2502
|
+
|
|
2272
2503
|
Conversation:
|
|
2273
2504
|
${conversationText}
|
|
2274
2505
|
|
|
2275
|
-
Summary
|
|
2276
|
-
|
|
2506
|
+
Summary:`;
|
|
2507
|
+
const template = config.promptTemplate || defaultPrompt;
|
|
2508
|
+
const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
|
|
2509
|
+
const summaryPrompt = [
|
|
2510
|
+
systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
|
|
2511
|
+
humanMessage(promptBody)
|
|
2277
2512
|
];
|
|
2278
2513
|
let summaryText = "Summary unavailable.";
|
|
2279
2514
|
try {
|
|
@@ -2283,8 +2518,22 @@ Summary:`
|
|
|
2283
2518
|
console.error("[ContextSummarize] Failed to generate summary:", err);
|
|
2284
2519
|
return {};
|
|
2285
2520
|
}
|
|
2521
|
+
const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
|
|
2522
|
+
messages.forEach((m, idx) => {
|
|
2523
|
+
if (m.role === "assistant" && m.tool_calls && Array.isArray(m.tool_calls)) {
|
|
2524
|
+
m.tool_calls.forEach((tc) => {
|
|
2525
|
+
if (tc.id) {
|
|
2526
|
+
toolCallIdToAssistantIdx.set(tc.id, idx);
|
|
2527
|
+
}
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
});
|
|
2286
2531
|
const newMessages = messages.map((m) => {
|
|
2287
2532
|
if (m.role === "tool") {
|
|
2533
|
+
const toolCallId = m.tool_call_id;
|
|
2534
|
+
if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
|
|
2535
|
+
return null;
|
|
2536
|
+
}
|
|
2288
2537
|
if (m.content === "SUMMARIZED") return m;
|
|
2289
2538
|
return {
|
|
2290
2539
|
...m,
|
|
@@ -2292,7 +2541,8 @@ Summary:`
|
|
|
2292
2541
|
};
|
|
2293
2542
|
}
|
|
2294
2543
|
return m;
|
|
2295
|
-
});
|
|
2544
|
+
}).filter((m) => m !== null);
|
|
2545
|
+
const validatedMessages = validateMessageSequence(newMessages);
|
|
2296
2546
|
const summaryToolCallId = `call_summary_${Date.now()}`;
|
|
2297
2547
|
const assistantSummaryCall = {
|
|
2298
2548
|
role: "assistant",
|
|
@@ -2316,8 +2566,10 @@ Summary:`
|
|
|
2316
2566
|
// Matches the tool name in the call
|
|
2317
2567
|
content: summaryText
|
|
2318
2568
|
};
|
|
2569
|
+
const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
|
|
2319
2570
|
return {
|
|
2320
|
-
messages: [...
|
|
2571
|
+
messages: [...validatedMessages, assistantSummaryCall, toolSummaryResponse],
|
|
2572
|
+
summaries
|
|
2321
2573
|
};
|
|
2322
2574
|
};
|
|
2323
2575
|
}
|
|
@@ -2422,9 +2674,29 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
2422
2674
|
maxTok = 5e4;
|
|
2423
2675
|
}
|
|
2424
2676
|
try {
|
|
2425
|
-
const allText = (state.messages || []).map((m) =>
|
|
2677
|
+
const allText = (state.messages || []).map((m) => {
|
|
2678
|
+
if (typeof m.content === "string") return m.content;
|
|
2679
|
+
if (Array.isArray(m.content)) {
|
|
2680
|
+
return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
|
|
2681
|
+
}
|
|
2682
|
+
if (m.content && typeof m.content === "object") {
|
|
2683
|
+
return JSON.stringify(m.content);
|
|
2684
|
+
}
|
|
2685
|
+
return "";
|
|
2686
|
+
}).join("\n");
|
|
2426
2687
|
const tokenCount = countApproxTokens(allText);
|
|
2427
2688
|
const needsSum = tokenCount > maxTok;
|
|
2689
|
+
if (process.env.DEBUG_SUMMARIZATION) {
|
|
2690
|
+
const msgCount = (state.messages || []).length;
|
|
2691
|
+
const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
|
|
2692
|
+
const toolDetails = toolMsgs.map((m, i) => {
|
|
2693
|
+
const contentStr = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
2694
|
+
return `[${i}] id:${m.tool_call_id?.slice(-8) || "none"} len:${contentStr.length} preview:"${contentStr.slice(0, 30)}..."`;
|
|
2695
|
+
});
|
|
2696
|
+
console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
|
|
2697
|
+
toolDetails.forEach((d) => console.log(` ${d}`));
|
|
2698
|
+
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
|
|
2699
|
+
}
|
|
2428
2700
|
return needsSum;
|
|
2429
2701
|
} catch (e) {
|
|
2430
2702
|
return false;
|
|
@@ -2482,7 +2754,8 @@ function createSmartAgent(opts) {
|
|
|
2482
2754
|
const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
|
|
2483
2755
|
let state = { ...input, messages: seedMessages };
|
|
2484
2756
|
let lastResult = null;
|
|
2485
|
-
const
|
|
2757
|
+
const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? opts.limits?.maxToolCalls ?? 10;
|
|
2758
|
+
const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
|
|
2486
2759
|
for (let i = 0; i < iterationLimit; i++) {
|
|
2487
2760
|
const next = summarizationEnabled ? decideBefore(state) : "agent";
|
|
2488
2761
|
if (next === "contextSummarize" && summarizer) {
|
|
@@ -2494,7 +2767,11 @@ function createSmartAgent(opts) {
|
|
|
2494
2767
|
}
|
|
2495
2768
|
const res = await base.invoke(state, config);
|
|
2496
2769
|
lastResult = res;
|
|
2770
|
+
const currentSummaries = state.summaries;
|
|
2497
2771
|
state = res.state || { ...state, messages: res.messages };
|
|
2772
|
+
if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
|
|
2773
|
+
state = { ...state, summaries: currentSummaries };
|
|
2774
|
+
}
|
|
2498
2775
|
if (state.ctx?.__needsSummarization && summarizer) {
|
|
2499
2776
|
const ctx = { ...state.ctx || {} };
|
|
2500
2777
|
delete ctx.__needsSummarization;
|
|
@@ -2522,6 +2799,13 @@ function createSmartAgent(opts) {
|
|
|
2522
2799
|
const res = await base.invoke(state, config);
|
|
2523
2800
|
lastResult = res;
|
|
2524
2801
|
}
|
|
2802
|
+
if (state.summaries && state.summaries.length > 0) {
|
|
2803
|
+
if (lastResult.state) {
|
|
2804
|
+
lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
|
|
2805
|
+
} else {
|
|
2806
|
+
lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2525
2809
|
return lastResult;
|
|
2526
2810
|
},
|
|
2527
2811
|
snapshot: base.snapshot,
|