@cognipeer/agent-sdk 0.2.2 → 0.2.4

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
  }
@@ -533,6 +550,7 @@ function ensureDir(p) {
533
550
  function withDefaults(config) {
534
551
  const enabled = Boolean(config?.enabled);
535
552
  const logData = config?.logData ?? DEFAULT_TRACE_CONFIG.logData;
553
+ const mode = config?.mode === "streaming" ? "streaming" : "batched";
536
554
  let sink;
537
555
  try {
538
556
  sink = resolveSink(config?.sink);
@@ -545,7 +563,7 @@ function withDefaults(config) {
545
563
  return {
546
564
  enabled,
547
565
  logData,
548
- mode: "batched",
566
+ mode,
549
567
  sink
550
568
  };
551
569
  }
@@ -616,20 +634,134 @@ async function postTraceSession(url, headers, payload) {
616
634
  if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
617
635
  finalHeaders["content-type"] = "application/json";
618
636
  }
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 {
637
+ try {
638
+ const response = await fetchFn(url, {
639
+ method: "POST",
640
+ headers: finalHeaders,
641
+ body: JSON.stringify(payload)
642
+ });
643
+ if (!response.ok) {
644
+ let responseText = "";
645
+ try {
646
+ responseText = await response.text();
647
+ } catch {
648
+ }
649
+ const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
650
+ const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
651
+ const errorMsg = `${statusLine}${bodyPreview}`;
652
+ if (typeof console !== "undefined" && typeof console.error === "function") {
653
+ console.error("[Tracing] Error posting to", url, ":", errorMsg);
654
+ }
655
+ throw new Error(errorMsg);
656
+ }
657
+ } catch (err) {
658
+ const errMsg = err instanceof Error ? err.message : String(err);
659
+ if (typeof console !== "undefined" && typeof console.error === "function") {
660
+ console.error("[Tracing] Failed to post:", errMsg);
661
+ }
662
+ throw err;
663
+ }
664
+ }
665
+ async function postStreamingSessionStart(baseUrl, headers, payload) {
666
+ const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
667
+ if (!fetchFn) {
668
+ throw new Error("HTTP sink requires fetch to be available in this runtime.");
669
+ }
670
+ const url = `${baseUrl.replace(/\/$/, "")}/${payload.sessionId}/start`;
671
+ const finalHeaders = { ...headers || {} };
672
+ if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
673
+ finalHeaders["content-type"] = "application/json";
674
+ }
675
+ try {
676
+ const response = await fetchFn(url, {
677
+ method: "POST",
678
+ headers: finalHeaders,
679
+ body: JSON.stringify(payload)
680
+ });
681
+ if (!response.ok) {
682
+ let responseText = "";
683
+ try {
684
+ responseText = await response.text();
685
+ } catch {
686
+ }
687
+ const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
688
+ const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
689
+ throw new Error(`${statusLine}${bodyPreview}`);
690
+ }
691
+ } catch (err) {
692
+ const errMsg = err instanceof Error ? err.message : String(err);
693
+ if (typeof console !== "undefined" && typeof console.error === "function") {
694
+ console.error("[Tracing] Failed to post session start:", errMsg);
695
+ }
696
+ throw err;
697
+ }
698
+ }
699
+ async function postStreamingEvent(baseUrl, headers, sessionId, event) {
700
+ const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
701
+ if (!fetchFn) {
702
+ throw new Error("HTTP sink requires fetch to be available in this runtime.");
703
+ }
704
+ const url = `${baseUrl.replace(/\/$/, "")}/${sessionId}/events`;
705
+ const finalHeaders = { ...headers || {} };
706
+ if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
707
+ finalHeaders["content-type"] = "application/json";
708
+ }
709
+ try {
710
+ const response = await fetchFn(url, {
711
+ method: "POST",
712
+ headers: finalHeaders,
713
+ body: JSON.stringify({ event })
714
+ });
715
+ if (!response.ok) {
716
+ let responseText = "";
717
+ try {
718
+ responseText = await response.text();
719
+ } catch {
720
+ }
721
+ const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
722
+ const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
723
+ throw new Error(`${statusLine}${bodyPreview}`);
724
+ }
725
+ } catch (err) {
726
+ const errMsg = err instanceof Error ? err.message : String(err);
727
+ if (typeof console !== "undefined" && typeof console.error === "function") {
728
+ console.error("[Tracing] Failed to post streaming event:", errMsg);
729
+ }
730
+ throw err;
731
+ }
732
+ }
733
+ async function postStreamingSessionEnd(baseUrl, headers, payload) {
734
+ const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
735
+ if (!fetchFn) {
736
+ throw new Error("HTTP sink requires fetch to be available in this runtime.");
737
+ }
738
+ const url = `${baseUrl.replace(/\/$/, "")}/${payload.sessionId}/end`;
739
+ const finalHeaders = { ...headers || {} };
740
+ if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
741
+ finalHeaders["content-type"] = "application/json";
742
+ }
743
+ try {
744
+ const response = await fetchFn(url, {
745
+ method: "POST",
746
+ headers: finalHeaders,
747
+ body: JSON.stringify(payload)
748
+ });
749
+ if (!response.ok) {
750
+ let responseText = "";
751
+ try {
752
+ responseText = await response.text();
753
+ } catch {
754
+ }
755
+ const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
756
+ const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
757
+ throw new Error(`${statusLine}${bodyPreview}`);
758
+ }
759
+ } catch (err) {
760
+ const errMsg = err instanceof Error ? err.message : String(err);
761
+ if (typeof console !== "undefined" && typeof console.error === "function") {
762
+ console.error("[Tracing] Failed to post session end:", errMsg);
629
763
  }
630
- const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
631
- const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
632
- throw new Error(`${statusLine}${bodyPreview}`);
764
+ throw err;
633
765
  }
634
766
  }
635
767
  function createEmptySummary() {
@@ -808,7 +940,8 @@ function createTraceSession(opts) {
808
940
  events: [],
809
941
  summary: createEmptySummary(),
810
942
  status: "in_progress",
811
- errors: []
943
+ errors: [],
944
+ sessionStarted: false
812
945
  };
813
946
  if (cfg.sink.type === "file") {
814
947
  const baseDir = cfg.sink.baseDir;
@@ -820,6 +953,49 @@ function createTraceSession(opts) {
820
953
  }
821
954
  return runtime;
822
955
  }
956
+ async function startStreamingSession(session, agentRuntime) {
957
+ if (!session) return;
958
+ if (session.resolvedConfig.mode !== "streaming") return;
959
+ if (session.sessionStarted) return;
960
+ const sink = session.resolvedConfig.sink;
961
+ const agentInfo = agentRuntime ? {
962
+ name: agentRuntime.name || "unknown-agent",
963
+ version: agentRuntime.version,
964
+ model: getModelName(agentRuntime.model) || "unknown-model",
965
+ provider: getProviderName(agentRuntime.model)
966
+ } : void 0;
967
+ session.agentInfo = agentInfo;
968
+ const configSnapshot = buildConfigSnapshot(session);
969
+ const startedAtIso = new Date(session.startedAt).toISOString();
970
+ const payload = {
971
+ sessionId: session.sessionId,
972
+ startedAt: startedAtIso,
973
+ agent: agentInfo,
974
+ config: configSnapshot
975
+ };
976
+ if (sink.type === "cognipeer" || sink.type === "http") {
977
+ try {
978
+ const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
979
+ await postStreamingSessionStart(sink.url, headers, payload);
980
+ session.sessionStarted = true;
981
+ } catch (err) {
982
+ const message = err instanceof Error ? err.message : String(err);
983
+ session.errors.push({
984
+ eventId: "session_start",
985
+ message,
986
+ type: "sink",
987
+ timestamp: startedAtIso
988
+ });
989
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
990
+ console.warn("[Tracing] Failed to start streaming session, falling back to batched:", message);
991
+ }
992
+ }
993
+ } else if (sink.type === "custom" && typeof sink.onEvent === "function") {
994
+ session.sessionStarted = true;
995
+ } else if (sink.type === "file") {
996
+ session.sessionStarted = true;
997
+ }
998
+ }
823
999
  function recordTraceEvent(session, event) {
824
1000
  if (!session) return void 0;
825
1001
  const sequence = session.events.length + 1;
@@ -922,6 +1098,23 @@ function recordTraceEvent(session, event) {
922
1098
  }
923
1099
  }
924
1100
  }
1101
+ if (session.resolvedConfig.mode === "streaming" && session.sessionStarted) {
1102
+ if (sink.type === "cognipeer" || sink.type === "http") {
1103
+ const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
1104
+ postStreamingEvent(sink.url, headers, session.sessionId, record).catch((err) => {
1105
+ const errMsg = err instanceof Error ? err.message : String(err);
1106
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
1107
+ console.warn("[Tracing] Failed to send streaming event:", errMsg);
1108
+ }
1109
+ session.errors.push({
1110
+ eventId: record.id,
1111
+ message: errMsg,
1112
+ type: "sink",
1113
+ timestamp: timestampIso
1114
+ });
1115
+ });
1116
+ }
1117
+ }
925
1118
  return record;
926
1119
  }
927
1120
  async function finalizeTraceSession(session, params = {}) {
@@ -930,12 +1123,16 @@ async function finalizeTraceSession(session, params = {}) {
930
1123
  const endedAtIso = new Date(endedAtMs).toISOString();
931
1124
  const durationMs = endedAtMs - session.startedAt;
932
1125
  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;
1126
+ const buildAgentInfo = (runtime) => {
1127
+ if (!runtime) return void 0;
1128
+ return {
1129
+ name: runtime.name || "unknown-agent",
1130
+ version: runtime.version,
1131
+ model: getModelName(runtime.model) || "unknown-model",
1132
+ provider: getProviderName(runtime.model)
1133
+ };
1134
+ };
1135
+ const agentInfo = buildAgentInfo(params.agentRuntime);
939
1136
  if (params.error) {
940
1137
  session.errors.push({
941
1138
  eventId: "session",
@@ -952,7 +1149,7 @@ async function finalizeTraceSession(session, params = {}) {
952
1149
  startedAt: startedAtIso,
953
1150
  endedAt: endedAtIso,
954
1151
  durationMs,
955
- agent: agentInfo,
1152
+ agent: agentInfo || session.agentInfo,
956
1153
  config: configSnapshot,
957
1154
  summary: session.summary,
958
1155
  events: session.events,
@@ -962,10 +1159,22 @@ async function finalizeTraceSession(session, params = {}) {
962
1159
  const payloadForSink = buildPayload(initialStatus);
963
1160
  let sinkFailed = false;
964
1161
  const sink = session.resolvedConfig.sink;
1162
+ const isStreamingMode = session.resolvedConfig.mode === "streaming";
965
1163
  if (sink.type === "cognipeer" || sink.type === "http") {
966
1164
  try {
967
1165
  const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
968
- await postTraceSession(sink.url, headers, payloadForSink);
1166
+ if (isStreamingMode && session.sessionStarted) {
1167
+ await postStreamingSessionEnd(sink.url, headers, {
1168
+ sessionId: session.sessionId,
1169
+ endedAt: endedAtIso,
1170
+ durationMs,
1171
+ status: initialStatus,
1172
+ summary: session.summary,
1173
+ errors: session.errors
1174
+ });
1175
+ } else {
1176
+ await postTraceSession(sink.url, headers, payloadForSink);
1177
+ }
969
1178
  } catch (err) {
970
1179
  sinkFailed = true;
971
1180
  const message = err instanceof Error ? err.message : String(err);
@@ -1103,12 +1312,29 @@ function createAgentCoreNode(opts) {
1103
1312
  };
1104
1313
  const normalizeBedrockToolPairing = (input) => {
1105
1314
  const msgs = Array.isArray(input) ? [...input] : [];
1106
- for (let i = 0; i < msgs.length; i++) {
1107
- const m = msgs[i];
1315
+ const validToolCallIds = /* @__PURE__ */ new Set();
1316
+ for (const m of msgs) {
1317
+ if (m?.role === "assistant") {
1318
+ for (const tu of extractToolUses(m)) {
1319
+ if (tu.id) validToolCallIds.add(tu.id);
1320
+ }
1321
+ }
1322
+ }
1323
+ const filteredMsgs = msgs.filter((m) => {
1324
+ if (m?.role === "tool") {
1325
+ const toolCallId = m.tool_call_id;
1326
+ if (!toolCallId || !validToolCallIds.has(toolCallId)) {
1327
+ return false;
1328
+ }
1329
+ }
1330
+ return true;
1331
+ });
1332
+ for (let i = 0; i < filteredMsgs.length; i++) {
1333
+ const m = filteredMsgs[i];
1108
1334
  if (m?.role !== "assistant") continue;
1109
1335
  const toolUses = extractToolUses(m);
1110
1336
  if (toolUses.length === 0) continue;
1111
- const next = msgs[i + 1];
1337
+ const next = filteredMsgs[i + 1];
1112
1338
  const firstId = toolUses[0]?.id;
1113
1339
  const nextIsToolForFirst = !!(next && next.role === "tool" && next.tool_call_id === firstId);
1114
1340
  if (nextIsToolForFirst) continue;
@@ -1116,12 +1342,12 @@ function createAgentCoreNode(opts) {
1116
1342
  role: "tool",
1117
1343
  name: tu.name || "unknown_tool",
1118
1344
  tool_call_id: tu.id,
1119
- content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for Bedrock tool_result adjacency."
1345
+ content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for tool_result adjacency."
1120
1346
  }));
1121
- msgs.splice(i + 1, 0, ...placeholders);
1347
+ filteredMsgs.splice(i + 1, 0, ...placeholders);
1122
1348
  i += placeholders.length;
1123
1349
  }
1124
- return msgs;
1350
+ return filteredMsgs;
1125
1351
  };
1126
1352
  const debugToolPairing = (messagesToSend) => {
1127
1353
  try {
@@ -1143,11 +1369,54 @@ function createAgentCoreNode(opts) {
1143
1369
  } catch (e) {
1144
1370
  }
1145
1371
  };
1372
+ const onEvent = state.ctx?.__onEvent;
1373
+ const onStream = state.ctx?.__onStream;
1374
+ const streamingEnabled = Boolean(state.ctx?.__streaming);
1375
+ const cancellationToken = state.ctx?.__cancellationToken;
1376
+ const abortSignal = state.ctx?.__abortSignal;
1377
+ const extractText = (chunk) => {
1378
+ if (chunk == null) return "";
1379
+ if (typeof chunk === "string") return chunk;
1380
+ if (typeof chunk?.content === "string") return chunk.content;
1381
+ if (Array.isArray(chunk?.content)) {
1382
+ return chunk.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
1383
+ }
1384
+ if (typeof chunk?.text === "string") return chunk.text;
1385
+ if (typeof chunk?.delta?.content === "string") return chunk.delta.content;
1386
+ return "";
1387
+ };
1146
1388
  let response;
1147
1389
  try {
1148
1390
  const normalizedMessages = normalizeBedrockToolPairing([...state.messages]);
1149
1391
  debugToolPairing(normalizedMessages);
1150
- response = await modelWithTools.invoke(normalizedMessages);
1392
+ if (streamingEnabled && typeof modelWithTools.stream === "function") {
1393
+ let streamedText = "";
1394
+ let streamedMessage;
1395
+ for await (const chunk of modelWithTools.stream(normalizedMessages, { signal: abortSignal, cancellationToken })) {
1396
+ if (cancellationToken && cancellationToken.isCancellationRequested || abortSignal?.aborted) {
1397
+ break;
1398
+ }
1399
+ if (chunk && typeof chunk === "object" && chunk.role) {
1400
+ streamedMessage = chunk;
1401
+ }
1402
+ const text = extractText(chunk);
1403
+ if (text) {
1404
+ streamedText += text;
1405
+ onStream?.({ text });
1406
+ onEvent?.({ type: "stream", text });
1407
+ }
1408
+ }
1409
+ if (streamedMessage) {
1410
+ response = { ...streamedMessage };
1411
+ if (response.content == null || response.content === "") {
1412
+ response.content = streamedText;
1413
+ }
1414
+ } else {
1415
+ response = { role: "assistant", content: streamedText };
1416
+ }
1417
+ } else {
1418
+ response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken });
1419
+ }
1151
1420
  } catch (err) {
1152
1421
  const durationMs2 = Date.now() - start;
1153
1422
  recordTraceEvent(traceSession, {
@@ -1168,7 +1437,7 @@ function createAgentCoreNode(opts) {
1168
1437
  ...state.messages,
1169
1438
  response
1170
1439
  ];
1171
- const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.usage;
1440
+ const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
1172
1441
  const normalized = normalizeUsage(rawUsage);
1173
1442
  const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
1174
1443
  const durationMs = Date.now() - start;
@@ -1235,6 +1504,9 @@ function createToolsNode(initialTools, opts) {
1235
1504
  };
1236
1505
  const appended = [];
1237
1506
  const onEvent = state.ctx?.__onEvent;
1507
+ const onProgress = state.ctx?.__onProgress;
1508
+ const cancellationToken = state.ctx?.__cancellationToken;
1509
+ const abortSignal = state.ctx?.__abortSignal;
1238
1510
  const traceSession = state.ctx?.__traceSession;
1239
1511
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
1240
1512
  const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
@@ -1291,7 +1563,20 @@ function createToolsNode(initialTools, opts) {
1291
1563
  name: tc.name
1292
1564
  });
1293
1565
  }
1566
+ const isCancelled = () => {
1567
+ if (abortSignal?.aborted) return { cancelled: true, reason: "aborted" };
1568
+ if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
1569
+ return { cancelled: false, reason: void 0 };
1570
+ };
1294
1571
  const runOne = async (tc) => {
1572
+ const cancelState = isCancelled();
1573
+ if (cancelState.cancelled) {
1574
+ const ctx = state.ctx = state.ctx || {};
1575
+ ctx.__cancelled = { stage: "tools", reason: cancelState.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
1576
+ onEvent?.({ type: "cancelled", stage: "tools", reason: cancelState.reason });
1577
+ onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
1578
+ return { status: "error" };
1579
+ }
1295
1580
  const t = toolByName.get(tc.name);
1296
1581
  if (!t) {
1297
1582
  onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
@@ -1370,15 +1655,17 @@ function createToolsNode(initialTools, opts) {
1370
1655
  const start = Date.now();
1371
1656
  try {
1372
1657
  onEvent?.({ type: "tool_call", phase: "start", name: t.name, id: tc.id, args });
1658
+ onProgress?.({ stage: "tools", message: `Running tool ${tc.name}` });
1373
1659
  const sanitizedArgs = sanitizeTracePayload(args);
1374
1660
  const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1375
1661
  let output;
1376
1662
  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);
1663
+ const callOptions = { cancellationToken, signal: abortSignal };
1664
+ if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
1665
+ else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
1666
+ else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
1667
+ else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
1668
+ else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
1382
1669
  else throw new Error("Tool is not invokable");
1383
1670
  if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
1384
1671
  const newRuntime = output.__handoff.runtime;
@@ -1408,6 +1695,7 @@ function createToolsNode(initialTools, opts) {
1408
1695
  toolHistory.push({ executionId, toolName: t.name, args, output, rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
1409
1696
  appended.push({ role: "tool", content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1410
1697
  onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: output, durationMs });
1698
+ onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
1411
1699
  if (needsApproval && approvalEntry) {
1412
1700
  approvalEntry.status = "executed";
1413
1701
  approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -1457,6 +1745,7 @@ function createToolsNode(initialTools, opts) {
1457
1745
  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
1746
  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
1747
  onEvent?.({ type: "tool_call", phase: "error", name: t.name, id: tc.id, args, error: { message: e?.message || String(e) } });
1748
+ onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: e?.message || String(e) } });
1460
1749
  const sanitizedArgs = sanitizeTracePayload(args);
1461
1750
  const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1462
1751
  const toolName = t.name || tc.name;
@@ -1500,6 +1789,14 @@ function createToolsNode(initialTools, opts) {
1500
1789
  };
1501
1790
  for (const tc of planned) {
1502
1791
  if (awaitingApproval) break;
1792
+ const cancelState = isCancelled();
1793
+ if (cancelState.cancelled) {
1794
+ const ctx = state.ctx = state.ctx || {};
1795
+ ctx.__cancelled = { stage: "tools", reason: cancelState.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
1796
+ onEvent?.({ type: "cancelled", stage: "tools", reason: cancelState.reason });
1797
+ onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
1798
+ break;
1799
+ }
1503
1800
  const result = await runOne(tc);
1504
1801
  if (result?.status === "awaiting_approval") {
1505
1802
  awaitingApproval = true;
@@ -1527,7 +1824,7 @@ function createToolsNode(initialTools, opts) {
1527
1824
  // src/nodes/toolLimitFinalize.ts
1528
1825
  function createToolLimitFinalizeNode(_opts) {
1529
1826
  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." };
1827
+ 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
1828
  const ctx = { ...state.ctx || {}, __finalizedDueToToolLimit: true };
1532
1829
  return { messages: [...state.messages, notice], ctx };
1533
1830
  };
@@ -1804,12 +2101,37 @@ function createAgent(opts) {
1804
2101
  delete nextCtx.__resumeStage;
1805
2102
  state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
1806
2103
  }
1807
- const maxToolCalls = opts.limits?.maxToolCalls === void 0 ? 50 : opts.limits?.maxToolCalls;
2104
+ const mergedLimits = {
2105
+ ...opts.limits || {},
2106
+ ...config?.limits || {}
2107
+ };
2108
+ const maxToolCalls = mergedLimits?.maxToolCalls === void 0 ? 50 : mergedLimits?.maxToolCalls;
1808
2109
  const iterationLimit = maxToolCalls === Infinity ? 100 : Math.max(maxToolCalls * 3 + 10, 40);
1809
2110
  let iterations = 0;
1810
2111
  const onStateChange = config?.onStateChange;
1811
2112
  const checkpointReason = config?.checkpointReason;
1812
2113
  let pausedStage = null;
2114
+ const onProgress = state.ctx?.__onProgress;
2115
+ const isCancelled = () => {
2116
+ const ctx = state.ctx || {};
2117
+ const token = ctx.__cancellationToken;
2118
+ const signal = ctx.__abortSignal;
2119
+ const deadline = ctx.__deadline;
2120
+ if (signal?.aborted) return { cancelled: true, reason: "aborted" };
2121
+ if (token && token.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
2122
+ if (deadline && Date.now() > deadline) return { cancelled: true, reason: "timeout" };
2123
+ return { cancelled: false, reason: void 0 };
2124
+ };
2125
+ const cancelIfRequested = (stage) => {
2126
+ const result = isCancelled();
2127
+ if (!result.cancelled) return false;
2128
+ const ctx = { ...state.ctx || {} };
2129
+ ctx.__cancelled = { stage, reason: result.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
2130
+ state = { ...state, ctx };
2131
+ emit?.({ type: "cancelled", stage, reason: result.reason });
2132
+ onProgress?.({ stage, message: "Cancelled", detail: { reason: result.reason } });
2133
+ return true;
2134
+ };
1813
2135
  const checkpointIfRequested = (stage) => {
1814
2136
  if (typeof onStateChange !== "function") return false;
1815
2137
  let result = false;
@@ -1832,8 +2154,10 @@ function createAgent(opts) {
1832
2154
  };
1833
2155
  while (iterations < iterationLimit) {
1834
2156
  iterations++;
2157
+ if (cancelIfRequested("loop")) break;
1835
2158
  const skippingAgent = resumeStage === "tools";
1836
2159
  if (!skippingAgent) {
2160
+ if (cancelIfRequested("before_guardrails")) break;
1837
2161
  if (checkpointIfRequested("before_guardrails")) break;
1838
2162
  const preGuardrails = getGuardrailConfig(state);
1839
2163
  if (preGuardrails.length > 0) {
@@ -1888,7 +2212,10 @@ function createAgent(opts) {
1888
2212
  break;
1889
2213
  }
1890
2214
  }
2215
+ onProgress?.({ stage: "agent", message: "Invoking model" });
2216
+ if (cancelIfRequested("before_agent")) break;
1891
2217
  state = { ...state, ...await agentCore(state) };
2218
+ onProgress?.({ stage: "agent", message: "Model response received" });
1892
2219
  if (checkpointIfRequested("after_agent")) break;
1893
2220
  const postGuardrails = getGuardrailConfig(state);
1894
2221
  if (postGuardrails.length > 0) {
@@ -1956,7 +2283,10 @@ function createAgent(opts) {
1956
2283
  continue;
1957
2284
  }
1958
2285
  if (toolCalls.length === 0) break;
2286
+ onProgress?.({ stage: "tools", message: "Running tools" });
2287
+ if (cancelIfRequested("before_tools")) break;
1959
2288
  state = { ...state, ...await toolsNode(state) };
2289
+ onProgress?.({ stage: "tools", message: "Tools finished" });
1960
2290
  if (state.ctx?.__awaitingApproval) break;
1961
2291
  if (checkpointIfRequested("after_tools")) break;
1962
2292
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
@@ -1997,15 +2327,39 @@ function createAgent(opts) {
1997
2327
  }
1998
2328
  const invokeAgent = async (input, config) => {
1999
2329
  const onEvent = config?.onEvent;
2330
+ const onProgress = config?.onProgress;
2331
+ const onStream = config?.onStream;
2332
+ const streamEnabled = config?.stream === true;
2000
2333
  const emit = (e) => {
2001
2334
  try {
2002
2335
  onEvent?.(e);
2003
2336
  } catch {
2004
2337
  }
2005
2338
  };
2339
+ const emitProgress = (progress) => {
2340
+ try {
2341
+ onProgress?.(progress);
2342
+ } catch {
2343
+ }
2344
+ emit({ type: "progress", ...progress });
2345
+ };
2006
2346
  const traceSession = createTraceSession(opts);
2007
- const ctx = { ...input.ctx || {}, __onEvent: onEvent };
2347
+ const ctx = {
2348
+ ...input.ctx || {},
2349
+ __onEvent: onEvent,
2350
+ __onProgress: emitProgress,
2351
+ __onStream: onStream,
2352
+ __streaming: streamEnabled
2353
+ };
2008
2354
  if (traceSession) ctx.__traceSession = traceSession;
2355
+ if (config?.cancellationToken) ctx.__cancellationToken = config.cancellationToken;
2356
+ if (config?.cancellationToken?.aborted !== void 0) {
2357
+ ctx.__abortSignal = config?.cancellationToken;
2358
+ }
2359
+ if (config?.timeoutMs && config.timeoutMs > 0) {
2360
+ ctx.__deadline = Date.now() + config.timeoutMs;
2361
+ }
2362
+ const runtimeWithInvokeLimits = config?.limits ? { ...runtime, limits: { ...runtime.limits || {}, ...config.limits } } : runtime;
2009
2363
  const initial = {
2010
2364
  messages: input.messages || [],
2011
2365
  toolCallCount: input.toolCallCount || 0,
@@ -2014,7 +2368,7 @@ function createAgent(opts) {
2014
2368
  metadata: input.metadata,
2015
2369
  ctx,
2016
2370
  pendingApprovals: input.pendingApprovals || [],
2017
- agent: input.agent || runtime,
2371
+ agent: input.agent || runtimeWithInvokeLimits,
2018
2372
  usage: input.usage || { perRequest: [], totals: {} }
2019
2373
  };
2020
2374
  let res;
@@ -2057,6 +2411,10 @@ function createAgent(opts) {
2057
2411
  }
2058
2412
  }
2059
2413
  emit({ type: "finalAnswer", content: typeof finalMsg?.content === "string" ? finalMsg.content : content });
2414
+ if (streamEnabled && content) {
2415
+ onStream?.({ text: content, isFinal: true });
2416
+ emit({ type: "stream", text: content, isFinal: true });
2417
+ }
2060
2418
  return {
2061
2419
  content,
2062
2420
  output: parsed,
@@ -2227,6 +2585,31 @@ function createAgentNode(opts) {
2227
2585
  // src/nodes/contextSummarize.ts
2228
2586
  var systemMessage = (content) => ({ role: "system", content });
2229
2587
  var humanMessage = (content) => ({ role: "user", content });
2588
+ function validateMessageSequence(messages) {
2589
+ const validated = [];
2590
+ const pendingToolCallIds = /* @__PURE__ */ new Set();
2591
+ for (const m of messages) {
2592
+ if (m.role === "assistant") {
2593
+ if (!m.tool_calls || m.tool_calls.length === 0) {
2594
+ pendingToolCallIds.clear();
2595
+ } else {
2596
+ m.tool_calls.forEach((tc) => {
2597
+ if (tc.id) pendingToolCallIds.add(tc.id);
2598
+ });
2599
+ }
2600
+ validated.push(m);
2601
+ } else if (m.role === "tool") {
2602
+ const toolCallId = m.tool_call_id;
2603
+ if (toolCallId && pendingToolCallIds.has(toolCallId)) {
2604
+ pendingToolCallIds.delete(toolCallId);
2605
+ validated.push(m);
2606
+ }
2607
+ } else {
2608
+ validated.push(m);
2609
+ }
2610
+ }
2611
+ return validated;
2612
+ }
2230
2613
  function getSummarizationConfig(opts) {
2231
2614
  if (typeof opts.summarization === "object") {
2232
2615
  return {
@@ -2234,13 +2617,15 @@ function getSummarizationConfig(opts) {
2234
2617
  maxTokens: opts.summarization.maxTokens ?? 5e4,
2235
2618
  // Max tokens we can send to the model for summarization prompt itself
2236
2619
  // This should be well under the model's context limit
2237
- summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3
2620
+ summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3,
2621
+ promptTemplate: opts.summarization.promptTemplate
2238
2622
  };
2239
2623
  }
2240
2624
  return {
2241
2625
  enabled: opts.summarization !== false,
2242
2626
  maxTokens: 5e4,
2243
- summaryPromptMaxTokens: 8e3
2627
+ summaryPromptMaxTokens: 8e3,
2628
+ promptTemplate: void 0
2244
2629
  };
2245
2630
  }
2246
2631
  function createContextSummarizeNode(opts) {
@@ -2249,10 +2634,14 @@ function createContextSummarizeNode(opts) {
2249
2634
  if (!config.enabled) return {};
2250
2635
  const model = opts.model;
2251
2636
  const messages = state.messages || [];
2637
+ const onEvent = state.ctx?.__onEvent;
2638
+ const traceSession = state.ctx?.__traceSession;
2252
2639
  const compressableMessages = messages.filter((m) => m.role === "tool" && m.content !== "SUMMARIZED");
2253
2640
  if (compressableMessages.length === 0) {
2254
2641
  return {};
2255
2642
  }
2643
+ const allTextBefore = 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");
2644
+ const tokenCountBefore = countApproxTokens(allTextBefore);
2256
2645
  const summaryPromptMaxTokens = config.summaryPromptMaxTokens;
2257
2646
  const availableTokensForConversation = summaryPromptMaxTokens - 500;
2258
2647
  const messageTexts = [];
@@ -2280,32 +2669,93 @@ function createContextSummarizeNode(opts) {
2280
2669
  accumulatedTokens += messageTokens;
2281
2670
  }
2282
2671
  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.
2672
+ const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
2673
+ const defaultPrompt = `Please summarize the following conversation history and update any previous summary.
2287
2674
  Focus on:
2288
2675
  - User goals and intent.
2289
2676
  - Key decisions made and actions taken.
2290
2677
  - Important tool outputs and data retrieved.
2291
2678
  - Current state of the task.
2292
2679
 
2680
+ Previous summary (if any):
2681
+ ${previousSummary || "(none)"}
2682
+
2293
2683
  Conversation:
2294
2684
  ${conversationText}
2295
2685
 
2296
- Summary:`
2297
- )
2686
+ Summary:`;
2687
+ const template = config.promptTemplate || defaultPrompt;
2688
+ const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
2689
+ const summaryPrompt = [
2690
+ systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
2691
+ humanMessage(promptBody)
2298
2692
  ];
2693
+ const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
2299
2694
  let summaryText = "Summary unavailable.";
2695
+ let outputTokensEstimate = 0;
2696
+ let durationMs = 0;
2697
+ let inputTokensActual;
2698
+ let outputTokensActual;
2699
+ let cachedInputTokens;
2700
+ let totalTokensActual;
2701
+ const startTime = Date.now();
2300
2702
  try {
2301
2703
  const response = await model.invoke(summaryPrompt);
2704
+ durationMs = Date.now() - startTime;
2302
2705
  summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2706
+ outputTokensEstimate = countApproxTokens(summaryText);
2707
+ const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2708
+ const normalized = normalizeUsage(rawUsage);
2709
+ if (normalized) {
2710
+ inputTokensActual = normalized.prompt_tokens;
2711
+ outputTokensActual = normalized.completion_tokens;
2712
+ totalTokensActual = normalized.total_tokens;
2713
+ cachedInputTokens = normalized.prompt_tokens_details?.cached_tokens;
2714
+ }
2303
2715
  } catch (err) {
2716
+ durationMs = Date.now() - startTime;
2304
2717
  console.error("[ContextSummarize] Failed to generate summary:", err);
2718
+ const errorEvent = {
2719
+ type: "summarization",
2720
+ summary: "",
2721
+ messagesCompressed: 0,
2722
+ inputTokens: inputTokensEstimate,
2723
+ outputTokens: 0,
2724
+ durationMs,
2725
+ previousSummary: previousSummary || void 0,
2726
+ tokenCountBefore,
2727
+ tokenCountAfter: tokenCountBefore
2728
+ };
2729
+ onEvent?.(errorEvent);
2730
+ recordTraceEvent(traceSession, {
2731
+ type: "summarization",
2732
+ label: "Context Summarization Failed",
2733
+ actor: { scope: "system", name: "summarizer", role: "summarization" },
2734
+ status: "error",
2735
+ durationMs,
2736
+ inputTokens: inputTokensEstimate,
2737
+ outputTokens: 0,
2738
+ error: { message: err?.message || String(err), stack: err?.stack },
2739
+ messageList: sanitizeTracePayload(summaryPrompt)
2740
+ });
2305
2741
  return {};
2306
2742
  }
2743
+ const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
2744
+ messages.forEach((m, idx) => {
2745
+ if (m.role === "assistant" && m.tool_calls && Array.isArray(m.tool_calls)) {
2746
+ m.tool_calls.forEach((tc) => {
2747
+ if (tc.id) {
2748
+ toolCallIdToAssistantIdx.set(tc.id, idx);
2749
+ }
2750
+ });
2751
+ }
2752
+ });
2307
2753
  const newMessages = messages.map((m) => {
2308
2754
  if (m.role === "tool") {
2755
+ const toolCallId = m.tool_call_id;
2756
+ if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
2757
+ return null;
2758
+ }
2309
2759
  if (m.content === "SUMMARIZED") return m;
2310
2760
  return {
2311
2761
  ...m,
@@ -2313,7 +2763,8 @@ Summary:`
2313
2763
  };
2314
2764
  }
2315
2765
  return m;
2316
- });
2766
+ }).filter((m) => m !== null);
2767
+ const validatedMessages = validateMessageSequence(newMessages);
2317
2768
  const summaryToolCallId = `call_summary_${Date.now()}`;
2318
2769
  const assistantSummaryCall = {
2319
2770
  role: "assistant",
@@ -2337,8 +2788,53 @@ Summary:`
2337
2788
  // Matches the tool name in the call
2338
2789
  content: summaryText
2339
2790
  };
2791
+ const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
2792
+ const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
2793
+ const allTextAfter = finalMessages.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");
2794
+ const tokenCountAfter = countApproxTokens(allTextAfter);
2795
+ const summarizationEvent = {
2796
+ type: "summarization",
2797
+ summary: summaryText,
2798
+ messagesCompressed: compressableMessages.length,
2799
+ inputTokens: inputTokensActual ?? inputTokensEstimate,
2800
+ outputTokens: outputTokensActual ?? outputTokensEstimate,
2801
+ cachedInputTokens,
2802
+ totalTokens: totalTokensActual ?? (inputTokensActual ?? inputTokensEstimate) + (outputTokensActual ?? outputTokensEstimate),
2803
+ durationMs,
2804
+ previousSummary: previousSummary || void 0,
2805
+ tokenCountBefore,
2806
+ tokenCountAfter,
2807
+ archivedCount: compressableMessages.length
2808
+ // deprecated field for backward compatibility
2809
+ };
2810
+ onEvent?.(summarizationEvent);
2811
+ recordTraceEvent(traceSession, {
2812
+ type: "summarization",
2813
+ label: "Context Summarization",
2814
+ actor: { scope: "system", name: "summarizer", role: "summarization" },
2815
+ status: "success",
2816
+ durationMs,
2817
+ inputTokens: inputTokensActual ?? inputTokensEstimate,
2818
+ outputTokens: outputTokensActual ?? outputTokensEstimate,
2819
+ cachedInputTokens,
2820
+ totalTokens: totalTokensActual,
2821
+ messageList: sanitizeTracePayload([
2822
+ ...summaryPrompt,
2823
+ { role: "assistant", content: summaryText, name: "summarization_result" }
2824
+ ]),
2825
+ debug: {
2826
+ messagesCompressed: compressableMessages.length,
2827
+ tokenCountBefore,
2828
+ tokenCountAfter,
2829
+ tokensSaved: tokenCountBefore - tokenCountAfter,
2830
+ previousSummaryLength: previousSummary?.length || 0,
2831
+ usageFromModel: inputTokensActual !== void 0,
2832
+ cachedInputTokens
2833
+ }
2834
+ });
2340
2835
  return {
2341
- messages: [...newMessages, assistantSummaryCall, toolSummaryResponse]
2836
+ messages: finalMessages,
2837
+ summaries
2342
2838
  };
2343
2839
  };
2344
2840
  }
@@ -2443,9 +2939,29 @@ function needsSummarization(state, opts, summarizationEnabled) {
2443
2939
  maxTok = 5e4;
2444
2940
  }
2445
2941
  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");
2942
+ const allText = (state.messages || []).map((m) => {
2943
+ if (typeof m.content === "string") return m.content;
2944
+ if (Array.isArray(m.content)) {
2945
+ return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
2946
+ }
2947
+ if (m.content && typeof m.content === "object") {
2948
+ return JSON.stringify(m.content);
2949
+ }
2950
+ return "";
2951
+ }).join("\n");
2447
2952
  const tokenCount = countApproxTokens(allText);
2448
2953
  const needsSum = tokenCount > maxTok;
2954
+ if (process.env.DEBUG_SUMMARIZATION) {
2955
+ const msgCount = (state.messages || []).length;
2956
+ const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
2957
+ const toolDetails = toolMsgs.map((m, i) => {
2958
+ const contentStr = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
2959
+ return `[${i}] id:${m.tool_call_id?.slice(-8) || "none"} len:${contentStr.length} preview:"${contentStr.slice(0, 30)}..."`;
2960
+ });
2961
+ console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
2962
+ toolDetails.forEach((d) => console.log(` ${d}`));
2963
+ console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
2964
+ }
2449
2965
  return needsSum;
2450
2966
  } catch (e) {
2451
2967
  return false;
@@ -2503,7 +3019,8 @@ function createSmartAgent(opts) {
2503
3019
  const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
2504
3020
  let state = { ...input, messages: seedMessages };
2505
3021
  let lastResult = null;
2506
- const iterationLimit = Math.max((opts.limits?.maxToolCalls ?? 10) * 3 + 5, 30);
3022
+ const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? opts.limits?.maxToolCalls ?? 10;
3023
+ const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
2507
3024
  for (let i = 0; i < iterationLimit; i++) {
2508
3025
  const next = summarizationEnabled ? decideBefore(state) : "agent";
2509
3026
  if (next === "contextSummarize" && summarizer) {
@@ -2515,7 +3032,11 @@ function createSmartAgent(opts) {
2515
3032
  }
2516
3033
  const res = await base.invoke(state, config);
2517
3034
  lastResult = res;
3035
+ const currentSummaries = state.summaries;
2518
3036
  state = res.state || { ...state, messages: res.messages };
3037
+ if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
3038
+ state = { ...state, summaries: currentSummaries };
3039
+ }
2519
3040
  if (state.ctx?.__needsSummarization && summarizer) {
2520
3041
  const ctx = { ...state.ctx || {} };
2521
3042
  delete ctx.__needsSummarization;
@@ -2543,6 +3064,13 @@ function createSmartAgent(opts) {
2543
3064
  const res = await base.invoke(state, config);
2544
3065
  lastResult = res;
2545
3066
  }
3067
+ if (state.summaries && state.summaries.length > 0) {
3068
+ if (lastResult.state) {
3069
+ lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
3070
+ } else {
3071
+ lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
3072
+ }
3073
+ }
2546
3074
  return lastResult;
2547
3075
  },
2548
3076
  snapshot: base.snapshot,
@@ -2832,6 +3360,6 @@ function createCodeGuardrail(options) {
2832
3360
  });
2833
3361
  }
2834
3362
 
2835
- export { GuardrailPhase, agentVerdictRule, applyTokenLimits, buildSystemPrompt, captureSnapshot, codePresenceRule, cognipeerSink, countApproxTokens, createAgent, createAgentNode, createCodeGuardrail, createContextSummarizeNode, createContextTools, createGuardrail, createJsonGuardrail, createRegexGuardrail, createResolverNode, createSmartAgent, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customSink, evaluateGuardrails, fileSink, fromLangchainModel, fromLangchainTools, httpSink, isSmartModel, jsonSchemaRule, regexRule, resolveToolApprovalState, restoreSnapshot, withTools };
3363
+ export { GuardrailPhase, agentVerdictRule, applyTokenLimits, buildSystemPrompt, captureSnapshot, codePresenceRule, cognipeerSink, countApproxTokens, createAgent, createAgentNode, createCodeGuardrail, createContextSummarizeNode, createContextTools, createGuardrail, createJsonGuardrail, createRegexGuardrail, createResolverNode, createSmartAgent, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customSink, evaluateGuardrails, fileSink, fromLangchainModel, fromLangchainTools, httpSink, isSmartModel, jsonSchemaRule, regexRule, resolveToolApprovalState, restoreSnapshot, startStreamingSession, withTools };
2836
3364
  //# sourceMappingURL=index.mjs.map
2837
3365
  //# sourceMappingURL=index.mjs.map