@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 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://api.cognipeer.com/v1/client/tracing/sessions";
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
- function firstNonEmptyString(...candidates) {
405
- for (const candidate of candidates) {
406
- const normalized = coerceToString(candidate);
407
- if (normalized) return normalized;
408
- }
409
- return void 0;
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 getModelName(model) {
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
- const direct = firstNonEmptyString(
415
- model?.model,
416
- model?.options?.model,
417
- model?.params?.model,
418
- model?.config?.model,
419
- model?.configuration?.model,
420
- model?.metadata?.model,
421
- model?.lc_kwargs?.model,
422
- maybeLc?.model,
423
- maybeLc?.options?.model,
424
- maybeLc?.params?.model,
425
- maybeLc?.config?.model,
426
- maybeLc?.configuration?.model,
427
- maybeLc?.metadata?.model,
428
- maybeLc?.lc_kwargs?.model,
429
- model?.metadata?.modelName,
430
- maybeLc?.metadata?.modelName,
431
- model?.modelName,
432
- maybeLc?.modelName,
433
- model?.lc_alias,
434
- model?.model_id,
435
- model?.id,
436
- maybeLc?.model_id,
437
- maybeLc?.id,
438
- model?._model,
439
- model?._modelName,
440
- model?.__model
441
- );
442
- if (direct) return direct;
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
- const clientValue = firstNonEmptyString(
445
- maybeClient?.model,
446
- maybeClient?.config?.model,
447
- maybeClient?.options?.model,
448
- maybeClient?.default?.model
449
- );
450
- if (clientValue) return clientValue;
451
- const fnNames = firstNonEmptyString(model?._modelId, model?._llmType, maybeLc?._modelId, maybeLc?._llmType, model?.constructor?.name);
452
- return fnNames;
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
- if (!model) return void 0;
456
- const direct = firstNonEmptyString(
457
- model?.provider,
458
- model?.options?.provider,
459
- model?.params?.provider,
460
- model?.config?.provider,
461
- model?.configuration?.provider,
462
- model?.metadata?.provider,
463
- model?.lc_kwargs?.provider,
464
- model?.lc_alias,
465
- model?.__provider
466
- );
467
- if (direct) return direct;
468
- const maybeLc = model?._lc;
469
- const lcValue = maybeLc ? firstNonEmptyString(
470
- maybeLc?.provider,
471
- maybeLc?.options?.provider,
472
- maybeLc?.config?.provider,
473
- maybeLc?.configuration?.provider,
474
- maybeLc?.metadata?.provider,
475
- maybeLc?.lc_kwargs?.provider,
476
- maybeLc?.client?.config?.provider
477
- ) : void 0;
478
- if (lcValue) return lcValue;
479
- const maybeClient = model?.client || model?.api || model?.service;
480
- return firstNonEmptyString(
481
- maybeClient?.provider,
482
- maybeClient?.config?.provider,
483
- maybeClient?.options?.provider,
484
- maybeClient?.metadata?.provider
485
- );
508
+ return extractMetadata(model, METADATA_PATHS.provider);
486
509
  }
487
510
  function inferModelFromMessages(messageList) {
488
511
  if (!Array.isArray(messageList)) return void 0;
489
- for (let i = messageList.length - 1; i >= 0; i -= 1) {
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 candidate = firstNonEmptyString(
493
- message?.metadata?.model,
494
- message?.metadata?.modelName,
495
- message?.metadata?.modelNames,
496
- message?.metadata?.model_id,
497
- message?.response_metadata?.model,
498
- message?.response_metadata?.modelName,
499
- message?.response_metadata?.modelNames,
500
- message?.response_metadata?.model_id,
501
- message?.usage_metadata?.model,
502
- message?.model,
503
- message?.modelName,
504
- message?.model_id,
505
- message?.additional_kwargs?.model,
506
- message?.additional_kwargs?.modelName,
507
- message?.info?.model,
508
- message?.annotations?.model
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
- for (let i = messageList.length - 1; i >= 0; i -= 1) {
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 candidate = firstNonEmptyString(
520
- message?.metadata?.provider,
521
- message?.metadata?.providers,
522
- message?.response_metadata?.provider,
523
- message?.response_metadata?.providers,
524
- message?.usage_metadata?.provider,
525
- message?.provider,
526
- message?.additional_kwargs?.provider,
527
- message?.info?.provider,
528
- message?.annotations?.provider
529
- );
530
- if (candidate) return candidate;
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
- const response = await fetchFn(url, {
629
- method: "POST",
630
- headers: finalHeaders,
631
- body: JSON.stringify(payload)
632
- });
633
- if (!response.ok) {
634
- let responseText = "";
635
- try {
636
- responseText = await response.text();
637
- } catch {
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
- const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
640
- const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
641
- throw new Error(`${statusLine}${bodyPreview}`);
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 agentInfo = params.agentRuntime ? {
943
- name: params.agentRuntime.name,
944
- version: params.agentRuntime.version,
945
- model: getModelName(params.agentRuntime.model),
946
- provider: getProviderName(params.agentRuntime.model)
947
- } : void 0;
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
- for (let i = 0; i < msgs.length; i++) {
1116
- const m = msgs[i];
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 = msgs[i + 1];
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 Bedrock tool_result adjacency."
1178
+ content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for tool_result adjacency."
1129
1179
  }));
1130
- msgs.splice(i + 1, 0, ...placeholders);
1180
+ filteredMsgs.splice(i + 1, 0, ...placeholders);
1131
1181
  i += placeholders.length;
1132
1182
  }
1133
- return msgs;
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
- response = await modelWithTools.invoke(normalizedMessages);
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
- if (typeof anyTool.func === "function") output = await anyTool.func(args);
1387
- else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args);
1388
- else if (typeof anyTool.call === "function") output = await anyTool.call(args);
1389
- else if (typeof anyTool._call === "function") output = await anyTool._call(args);
1390
- else if (typeof anyTool.run === "function") output = await anyTool.run(args);
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: "system", content: "Tool-call limit reached. Produce the best possible final answer using the available context and prior tool outputs. Do not call any more tools." };
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 maxToolCalls = opts.limits?.maxToolCalls === void 0 ? 50 : opts.limits?.maxToolCalls;
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 = { ...input.ctx || {}, __onEvent: onEvent };
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 || runtime,
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 conversationText = messages.map((m) => {
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
- return `${prefix}: ${content}`;
2270
- }).join("\n\n");
2271
- const summaryPrompt = [
2272
- systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
2273
- humanMessage(
2274
- `Please summarize the following conversation history.
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: [...newMessages, assistantSummaryCall, toolSummaryResponse]
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) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("") : "").join("\n");
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 iterationLimit = Math.max((opts.limits?.maxToolCalls ?? 10) * 3 + 5, 30);
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,