@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.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://api.cognipeer.com/v1/client/tracing/sessions";
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
- function firstNonEmptyString(...candidates) {
396
- for (const candidate of candidates) {
397
- const normalized = coerceToString(candidate);
398
- if (normalized) return normalized;
399
- }
400
- return void 0;
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 getModelName(model) {
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
- const direct = firstNonEmptyString(
406
- model?.model,
407
- model?.options?.model,
408
- model?.params?.model,
409
- model?.config?.model,
410
- model?.configuration?.model,
411
- model?.metadata?.model,
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
- const clientValue = firstNonEmptyString(
436
- maybeClient?.model,
437
- maybeClient?.config?.model,
438
- maybeClient?.options?.model,
439
- maybeClient?.default?.model
440
- );
441
- if (clientValue) return clientValue;
442
- const fnNames = firstNonEmptyString(model?._modelId, model?._llmType, maybeLc?._modelId, maybeLc?._llmType, model?.constructor?.name);
443
- return fnNames;
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
- if (!model) return void 0;
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
- for (let i = messageList.length - 1; i >= 0; i -= 1) {
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 candidate = firstNonEmptyString(
484
- message?.metadata?.model,
485
- message?.metadata?.modelName,
486
- message?.metadata?.modelNames,
487
- message?.metadata?.model_id,
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
- for (let i = messageList.length - 1; i >= 0; i -= 1) {
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 candidate = firstNonEmptyString(
511
- message?.metadata?.provider,
512
- message?.metadata?.providers,
513
- message?.response_metadata?.provider,
514
- message?.response_metadata?.providers,
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
- const response = await fetchFn(url, {
620
- method: "POST",
621
- headers: finalHeaders,
622
- body: JSON.stringify(payload)
623
- });
624
- if (!response.ok) {
625
- let responseText = "";
626
- try {
627
- responseText = await response.text();
628
- } catch {
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);
655
+ }
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);
629
660
  }
630
- const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
631
- const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
632
- throw new Error(`${statusLine}${bodyPreview}`);
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 agentInfo = params.agentRuntime ? {
934
- name: params.agentRuntime.name,
935
- version: params.agentRuntime.version,
936
- model: getModelName(params.agentRuntime.model),
937
- provider: getProviderName(params.agentRuntime.model)
938
- } : void 0;
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
- for (let i = 0; i < msgs.length; i++) {
1107
- const m = msgs[i];
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 = msgs[i + 1];
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 Bedrock tool_result adjacency."
1169
+ content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for tool_result adjacency."
1120
1170
  }));
1121
- msgs.splice(i + 1, 0, ...placeholders);
1171
+ filteredMsgs.splice(i + 1, 0, ...placeholders);
1122
1172
  i += placeholders.length;
1123
1173
  }
1124
- return msgs;
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
- response = await modelWithTools.invoke(normalizedMessages);
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
- if (typeof anyTool.func === "function") output = await anyTool.func(args);
1378
- else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args);
1379
- else if (typeof anyTool.call === "function") output = await anyTool.call(args);
1380
- else if (typeof anyTool._call === "function") output = await anyTool._call(args);
1381
- else if (typeof anyTool.run === "function") output = await anyTool.run(args);
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: "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." };
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 maxToolCalls = opts.limits?.maxToolCalls === void 0 ? 50 : opts.limits?.maxToolCalls;
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 = { ...input.ctx || {}, __onEvent: onEvent };
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 || runtime,
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,6 +2409,31 @@ 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 {
@@ -2234,13 +2441,15 @@ function getSummarizationConfig(opts) {
2234
2441
  maxTokens: opts.summarization.maxTokens ?? 5e4,
2235
2442
  // Max tokens we can send to the model for summarization prompt itself
2236
2443
  // This should be well under the model's context limit
2237
- summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3
2444
+ summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3,
2445
+ promptTemplate: opts.summarization.promptTemplate
2238
2446
  };
2239
2447
  }
2240
2448
  return {
2241
2449
  enabled: opts.summarization !== false,
2242
2450
  maxTokens: 5e4,
2243
- summaryPromptMaxTokens: 8e3
2451
+ summaryPromptMaxTokens: 8e3,
2452
+ promptTemplate: void 0
2244
2453
  };
2245
2454
  }
2246
2455
  function createContextSummarizeNode(opts) {
@@ -2280,21 +2489,26 @@ function createContextSummarizeNode(opts) {
2280
2489
  accumulatedTokens += messageTokens;
2281
2490
  }
2282
2491
  const conversationText = messageTexts.join("\n\n");
2283
- const summaryPrompt = [
2284
- systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
2285
- humanMessage(
2286
- `Please summarize the following conversation history.
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.
2287
2494
  Focus on:
2288
2495
  - User goals and intent.
2289
2496
  - Key decisions made and actions taken.
2290
2497
  - Important tool outputs and data retrieved.
2291
2498
  - Current state of the task.
2292
2499
 
2500
+ Previous summary (if any):
2501
+ ${previousSummary || "(none)"}
2502
+
2293
2503
  Conversation:
2294
2504
  ${conversationText}
2295
2505
 
2296
- Summary:`
2297
- )
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)
2298
2512
  ];
2299
2513
  let summaryText = "Summary unavailable.";
2300
2514
  try {
@@ -2304,8 +2518,22 @@ Summary:`
2304
2518
  console.error("[ContextSummarize] Failed to generate summary:", err);
2305
2519
  return {};
2306
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
+ });
2307
2531
  const newMessages = messages.map((m) => {
2308
2532
  if (m.role === "tool") {
2533
+ const toolCallId = m.tool_call_id;
2534
+ if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
2535
+ return null;
2536
+ }
2309
2537
  if (m.content === "SUMMARIZED") return m;
2310
2538
  return {
2311
2539
  ...m,
@@ -2313,7 +2541,8 @@ Summary:`
2313
2541
  };
2314
2542
  }
2315
2543
  return m;
2316
- });
2544
+ }).filter((m) => m !== null);
2545
+ const validatedMessages = validateMessageSequence(newMessages);
2317
2546
  const summaryToolCallId = `call_summary_${Date.now()}`;
2318
2547
  const assistantSummaryCall = {
2319
2548
  role: "assistant",
@@ -2337,8 +2566,10 @@ Summary:`
2337
2566
  // Matches the tool name in the call
2338
2567
  content: summaryText
2339
2568
  };
2569
+ const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
2340
2570
  return {
2341
- messages: [...newMessages, assistantSummaryCall, toolSummaryResponse]
2571
+ messages: [...validatedMessages, assistantSummaryCall, toolSummaryResponse],
2572
+ summaries
2342
2573
  };
2343
2574
  };
2344
2575
  }
@@ -2443,9 +2674,29 @@ function needsSummarization(state, opts, summarizationEnabled) {
2443
2674
  maxTok = 5e4;
2444
2675
  }
2445
2676
  try {
2446
- 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");
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");
2447
2687
  const tokenCount = countApproxTokens(allText);
2448
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
+ }
2449
2700
  return needsSum;
2450
2701
  } catch (e) {
2451
2702
  return false;
@@ -2503,7 +2754,8 @@ function createSmartAgent(opts) {
2503
2754
  const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
2504
2755
  let state = { ...input, messages: seedMessages };
2505
2756
  let lastResult = null;
2506
- const iterationLimit = Math.max((opts.limits?.maxToolCalls ?? 10) * 3 + 5, 30);
2757
+ const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? opts.limits?.maxToolCalls ?? 10;
2758
+ const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
2507
2759
  for (let i = 0; i < iterationLimit; i++) {
2508
2760
  const next = summarizationEnabled ? decideBefore(state) : "agent";
2509
2761
  if (next === "contextSummarize" && summarizer) {
@@ -2515,7 +2767,11 @@ function createSmartAgent(opts) {
2515
2767
  }
2516
2768
  const res = await base.invoke(state, config);
2517
2769
  lastResult = res;
2770
+ const currentSummaries = state.summaries;
2518
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
+ }
2519
2775
  if (state.ctx?.__needsSummarization && summarizer) {
2520
2776
  const ctx = { ...state.ctx || {} };
2521
2777
  delete ctx.__needsSummarization;
@@ -2543,6 +2799,13 @@ function createSmartAgent(opts) {
2543
2799
  const res = await base.invoke(state, config);
2544
2800
  lastResult = res;
2545
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
+ }
2546
2809
  return lastResult;
2547
2810
  },
2548
2811
  snapshot: base.snapshot,