@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.cjs CHANGED
@@ -143,7 +143,7 @@ function withTools(model, tools) {
143
143
  function fromLangchainModel(lcModel) {
144
144
  if (!lcModel) throw new Error("fromLangchainModel: model is undefined/null");
145
145
  const adapted = {
146
- invoke: async (messages) => {
146
+ invoke: async (messages, options) => {
147
147
  const normalizeContent = (content) => {
148
148
  if (content == null) return content;
149
149
  if (typeof content === "string") return content;
@@ -176,7 +176,7 @@ function fromLangchainModel(lcModel) {
176
176
  return { role, content: normalizeContent(m.content), name: m.name, tool_calls: m.tool_calls, tool_call_id: m.tool_call_id };
177
177
  };
178
178
  const lcMessages = messages.map(toLC);
179
- const response = await lcModel.invoke(lcMessages);
179
+ const response = await lcModel.invoke(lcMessages, options);
180
180
  if (response && typeof response === "object") {
181
181
  const content = response.content ?? response.text ?? "";
182
182
  return {
@@ -190,6 +190,43 @@ function fromLangchainModel(lcModel) {
190
190
  }
191
191
  return { role: "assistant", content: String(response ?? "") };
192
192
  },
193
+ stream: async function* (messages, options) {
194
+ if (typeof lcModel.stream !== "function") return;
195
+ const normalizeContent = (content) => {
196
+ if (content == null) return content;
197
+ if (typeof content === "string") return content;
198
+ if (Array.isArray(content)) {
199
+ return content.map((part) => {
200
+ if (!part || typeof part !== "object") return part;
201
+ if (part.type === "text") return { type: "text", text: String(part.text ?? "") };
202
+ if (part.type === "image_url") {
203
+ const img = part.image_url;
204
+ if (typeof img === "string") return { type: "image_url", image_url: { url: img } };
205
+ if (img && typeof img === "object") {
206
+ if ("url" in img) return { type: "image_url", image_url: img };
207
+ if ("base64" in img) {
208
+ const media = img.media_type || "image/jpeg";
209
+ const dataUrl = `data:${media};base64,${img.base64}`;
210
+ const detail = img.detail;
211
+ return { type: "image_url", image_url: { url: dataUrl, detail } };
212
+ }
213
+ }
214
+ return part;
215
+ }
216
+ return part;
217
+ });
218
+ }
219
+ return content;
220
+ };
221
+ const toLC = (m) => {
222
+ if (m._getType || m.lc_serializable) return m;
223
+ return { role: m.role, content: normalizeContent(m.content), name: m.name, tool_calls: m.tool_calls, tool_call_id: m.tool_call_id };
224
+ };
225
+ const lcMessages = messages.map(toLC);
226
+ for await (const chunk of lcModel.stream(lcMessages, options)) {
227
+ yield chunk;
228
+ }
229
+ },
193
230
  bindTools: (tools) => {
194
231
  const lcReady = toLangchainTools(tools);
195
232
  if (typeof lcModel.bindTools === "function") {
@@ -343,7 +380,7 @@ function safe(v) {
343
380
  const n = Number(v);
344
381
  return Number.isFinite(n) && n >= 0 ? n : 0;
345
382
  }
346
- var DEFAULT_COGNIPEER_URL = "https://api.cognipeer.com/v1/client/tracing/sessions";
383
+ var DEFAULT_COGNIPEER_URL = "https://console.cognipeer.com/api/client/v1/tracing/sessions";
347
384
  function fileSink(path2) {
348
385
  return { type: "file", path: path2 };
349
386
  }
@@ -401,133 +438,113 @@ function coerceToString(value) {
401
438
  }
402
439
  return void 0;
403
440
  }
404
- function firstNonEmptyString(...candidates) {
405
- for (const candidate of candidates) {
406
- const normalized = coerceToString(candidate);
407
- if (normalized) return normalized;
408
- }
409
- return void 0;
441
+ var METADATA_PATHS = {
442
+ model: [
443
+ "model",
444
+ "options.model",
445
+ "params.model",
446
+ "config.model",
447
+ "configuration.model",
448
+ "metadata.model",
449
+ "lc_kwargs.model",
450
+ "metadata.modelName",
451
+ "modelName",
452
+ "lc_alias",
453
+ "model_id",
454
+ "id",
455
+ "_model",
456
+ "_modelName",
457
+ "__model",
458
+ "_modelId",
459
+ "_llmType"
460
+ ],
461
+ provider: [
462
+ "provider",
463
+ "options.provider",
464
+ "params.provider",
465
+ "config.provider",
466
+ "configuration.provider",
467
+ "metadata.provider",
468
+ "lc_kwargs.provider"
469
+ ]
470
+ };
471
+ function getNestedValue(obj, path2) {
472
+ if (!obj || typeof path2 !== "string") return void 0;
473
+ return path2.split(".").reduce((current, key) => current?.[key], obj);
410
474
  }
411
- function getModelName(model) {
475
+ function extractMetadata(model, paths) {
412
476
  if (!model) return void 0;
477
+ for (const path2 of paths) {
478
+ const value = getNestedValue(model, path2);
479
+ const str = coerceToString(value);
480
+ if (str) return str;
481
+ }
413
482
  const maybeLc = model?._lc;
414
- const direct = firstNonEmptyString(
415
- model?.model,
416
- model?.options?.model,
417
- model?.params?.model,
418
- model?.config?.model,
419
- model?.configuration?.model,
420
- model?.metadata?.model,
421
- model?.lc_kwargs?.model,
422
- maybeLc?.model,
423
- maybeLc?.options?.model,
424
- maybeLc?.params?.model,
425
- maybeLc?.config?.model,
426
- maybeLc?.configuration?.model,
427
- maybeLc?.metadata?.model,
428
- maybeLc?.lc_kwargs?.model,
429
- model?.metadata?.modelName,
430
- maybeLc?.metadata?.modelName,
431
- model?.modelName,
432
- maybeLc?.modelName,
433
- model?.lc_alias,
434
- model?.model_id,
435
- model?.id,
436
- maybeLc?.model_id,
437
- maybeLc?.id,
438
- model?._model,
439
- model?._modelName,
440
- model?.__model
441
- );
442
- if (direct) return direct;
483
+ if (maybeLc) {
484
+ for (const path2 of paths) {
485
+ const value = getNestedValue(maybeLc, path2);
486
+ const str = coerceToString(value);
487
+ if (str) return str;
488
+ }
489
+ }
443
490
  const maybeClient = model?.client || model?.api || model?.service;
444
- const clientValue = firstNonEmptyString(
445
- maybeClient?.model,
446
- maybeClient?.config?.model,
447
- maybeClient?.options?.model,
448
- maybeClient?.default?.model
449
- );
450
- if (clientValue) return clientValue;
451
- const fnNames = firstNonEmptyString(model?._modelId, model?._llmType, maybeLc?._modelId, maybeLc?._llmType, model?.constructor?.name);
452
- return fnNames;
491
+ if (maybeClient) {
492
+ for (const path2 of paths) {
493
+ const value = getNestedValue(maybeClient, path2);
494
+ const str = coerceToString(value);
495
+ if (str) return str;
496
+ }
497
+ }
498
+ const constructorName = model?.constructor?.name;
499
+ if (constructorName && !["Object", "Function"].includes(constructorName)) {
500
+ return constructorName;
501
+ }
502
+ return void 0;
503
+ }
504
+ function getModelName(model) {
505
+ return extractMetadata(model, METADATA_PATHS.model);
453
506
  }
454
507
  function getProviderName(model) {
455
- if (!model) return void 0;
456
- const direct = firstNonEmptyString(
457
- model?.provider,
458
- model?.options?.provider,
459
- model?.params?.provider,
460
- model?.config?.provider,
461
- model?.configuration?.provider,
462
- model?.metadata?.provider,
463
- model?.lc_kwargs?.provider,
464
- model?.lc_alias,
465
- model?.__provider
466
- );
467
- if (direct) return direct;
468
- const maybeLc = model?._lc;
469
- const lcValue = maybeLc ? firstNonEmptyString(
470
- maybeLc?.provider,
471
- maybeLc?.options?.provider,
472
- maybeLc?.config?.provider,
473
- maybeLc?.configuration?.provider,
474
- maybeLc?.metadata?.provider,
475
- maybeLc?.lc_kwargs?.provider,
476
- maybeLc?.client?.config?.provider
477
- ) : void 0;
478
- if (lcValue) return lcValue;
479
- const maybeClient = model?.client || model?.api || model?.service;
480
- return firstNonEmptyString(
481
- maybeClient?.provider,
482
- maybeClient?.config?.provider,
483
- maybeClient?.options?.provider,
484
- maybeClient?.metadata?.provider
485
- );
508
+ return extractMetadata(model, METADATA_PATHS.provider);
486
509
  }
487
510
  function inferModelFromMessages(messageList) {
488
511
  if (!Array.isArray(messageList)) return void 0;
489
- for (let i = messageList.length - 1; i >= 0; i -= 1) {
512
+ const messagePaths = [
513
+ "metadata.model",
514
+ "metadata.modelName",
515
+ "response_metadata.model",
516
+ "response_metadata.modelName",
517
+ "usage_metadata.model",
518
+ "model",
519
+ "modelName"
520
+ ];
521
+ for (let i = messageList.length - 1; i >= 0; i--) {
490
522
  const message = messageList[i];
491
523
  if (!message) continue;
492
- const candidate = firstNonEmptyString(
493
- message?.metadata?.model,
494
- message?.metadata?.modelName,
495
- message?.metadata?.modelNames,
496
- message?.metadata?.model_id,
497
- message?.response_metadata?.model,
498
- message?.response_metadata?.modelName,
499
- message?.response_metadata?.modelNames,
500
- message?.response_metadata?.model_id,
501
- message?.usage_metadata?.model,
502
- message?.model,
503
- message?.modelName,
504
- message?.model_id,
505
- message?.additional_kwargs?.model,
506
- message?.additional_kwargs?.modelName,
507
- message?.info?.model,
508
- message?.annotations?.model
509
- );
510
- if (candidate) return candidate;
524
+ for (const path2 of messagePaths) {
525
+ const value = getNestedValue(message, path2);
526
+ const str = coerceToString(value);
527
+ if (str) return str;
528
+ }
511
529
  }
512
530
  return void 0;
513
531
  }
514
532
  function inferProviderFromMessages(messageList) {
515
533
  if (!Array.isArray(messageList)) return void 0;
516
- for (let i = messageList.length - 1; i >= 0; i -= 1) {
534
+ const messagePaths = [
535
+ "metadata.provider",
536
+ "response_metadata.provider",
537
+ "usage_metadata.provider",
538
+ "provider"
539
+ ];
540
+ for (let i = messageList.length - 1; i >= 0; i--) {
517
541
  const message = messageList[i];
518
542
  if (!message) continue;
519
- const candidate = firstNonEmptyString(
520
- message?.metadata?.provider,
521
- message?.metadata?.providers,
522
- message?.response_metadata?.provider,
523
- message?.response_metadata?.providers,
524
- message?.usage_metadata?.provider,
525
- message?.provider,
526
- message?.additional_kwargs?.provider,
527
- message?.info?.provider,
528
- message?.annotations?.provider
529
- );
530
- if (candidate) return candidate;
543
+ for (const path2 of messagePaths) {
544
+ const value = getNestedValue(message, path2);
545
+ const str = coerceToString(value);
546
+ if (str) return str;
547
+ }
531
548
  }
532
549
  return void 0;
533
550
  }
@@ -542,6 +559,7 @@ function ensureDir(p) {
542
559
  function withDefaults(config) {
543
560
  const enabled = Boolean(config?.enabled);
544
561
  const logData = config?.logData ?? DEFAULT_TRACE_CONFIG.logData;
562
+ const mode = config?.mode === "streaming" ? "streaming" : "batched";
545
563
  let sink;
546
564
  try {
547
565
  sink = resolveSink(config?.sink);
@@ -554,7 +572,7 @@ function withDefaults(config) {
554
572
  return {
555
573
  enabled,
556
574
  logData,
557
- mode: "batched",
575
+ mode,
558
576
  sink
559
577
  };
560
578
  }
@@ -625,20 +643,134 @@ async function postTraceSession(url, headers, payload) {
625
643
  if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
626
644
  finalHeaders["content-type"] = "application/json";
627
645
  }
628
- const response = await fetchFn(url, {
629
- method: "POST",
630
- headers: finalHeaders,
631
- body: JSON.stringify(payload)
632
- });
633
- if (!response.ok) {
634
- let responseText = "";
635
- try {
636
- responseText = await response.text();
637
- } catch {
646
+ try {
647
+ const response = await fetchFn(url, {
648
+ method: "POST",
649
+ headers: finalHeaders,
650
+ body: JSON.stringify(payload)
651
+ });
652
+ if (!response.ok) {
653
+ let responseText = "";
654
+ try {
655
+ responseText = await response.text();
656
+ } catch {
657
+ }
658
+ const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
659
+ const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
660
+ const errorMsg = `${statusLine}${bodyPreview}`;
661
+ if (typeof console !== "undefined" && typeof console.error === "function") {
662
+ console.error("[Tracing] Error posting to", url, ":", errorMsg);
663
+ }
664
+ throw new Error(errorMsg);
665
+ }
666
+ } catch (err) {
667
+ const errMsg = err instanceof Error ? err.message : String(err);
668
+ if (typeof console !== "undefined" && typeof console.error === "function") {
669
+ console.error("[Tracing] Failed to post:", errMsg);
670
+ }
671
+ throw err;
672
+ }
673
+ }
674
+ async function postStreamingSessionStart(baseUrl, headers, payload) {
675
+ const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
676
+ if (!fetchFn) {
677
+ throw new Error("HTTP sink requires fetch to be available in this runtime.");
678
+ }
679
+ const url = `${baseUrl.replace(/\/$/, "")}/${payload.sessionId}/start`;
680
+ const finalHeaders = { ...headers || {} };
681
+ if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
682
+ finalHeaders["content-type"] = "application/json";
683
+ }
684
+ try {
685
+ const response = await fetchFn(url, {
686
+ method: "POST",
687
+ headers: finalHeaders,
688
+ body: JSON.stringify(payload)
689
+ });
690
+ if (!response.ok) {
691
+ let responseText = "";
692
+ try {
693
+ responseText = await response.text();
694
+ } catch {
695
+ }
696
+ const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
697
+ const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
698
+ throw new Error(`${statusLine}${bodyPreview}`);
699
+ }
700
+ } catch (err) {
701
+ const errMsg = err instanceof Error ? err.message : String(err);
702
+ if (typeof console !== "undefined" && typeof console.error === "function") {
703
+ console.error("[Tracing] Failed to post session start:", errMsg);
704
+ }
705
+ throw err;
706
+ }
707
+ }
708
+ async function postStreamingEvent(baseUrl, headers, sessionId, event) {
709
+ const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
710
+ if (!fetchFn) {
711
+ throw new Error("HTTP sink requires fetch to be available in this runtime.");
712
+ }
713
+ const url = `${baseUrl.replace(/\/$/, "")}/${sessionId}/events`;
714
+ const finalHeaders = { ...headers || {} };
715
+ if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
716
+ finalHeaders["content-type"] = "application/json";
717
+ }
718
+ try {
719
+ const response = await fetchFn(url, {
720
+ method: "POST",
721
+ headers: finalHeaders,
722
+ body: JSON.stringify({ event })
723
+ });
724
+ if (!response.ok) {
725
+ let responseText = "";
726
+ try {
727
+ responseText = await response.text();
728
+ } catch {
729
+ }
730
+ const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
731
+ const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
732
+ throw new Error(`${statusLine}${bodyPreview}`);
733
+ }
734
+ } catch (err) {
735
+ const errMsg = err instanceof Error ? err.message : String(err);
736
+ if (typeof console !== "undefined" && typeof console.error === "function") {
737
+ console.error("[Tracing] Failed to post streaming event:", errMsg);
738
+ }
739
+ throw err;
740
+ }
741
+ }
742
+ async function postStreamingSessionEnd(baseUrl, headers, payload) {
743
+ const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
744
+ if (!fetchFn) {
745
+ throw new Error("HTTP sink requires fetch to be available in this runtime.");
746
+ }
747
+ const url = `${baseUrl.replace(/\/$/, "")}/${payload.sessionId}/end`;
748
+ const finalHeaders = { ...headers || {} };
749
+ if (!Object.keys(finalHeaders).some((key) => key.toLowerCase() === "content-type")) {
750
+ finalHeaders["content-type"] = "application/json";
751
+ }
752
+ try {
753
+ const response = await fetchFn(url, {
754
+ method: "POST",
755
+ headers: finalHeaders,
756
+ body: JSON.stringify(payload)
757
+ });
758
+ if (!response.ok) {
759
+ let responseText = "";
760
+ try {
761
+ responseText = await response.text();
762
+ } catch {
763
+ }
764
+ const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
765
+ const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
766
+ throw new Error(`${statusLine}${bodyPreview}`);
767
+ }
768
+ } catch (err) {
769
+ const errMsg = err instanceof Error ? err.message : String(err);
770
+ if (typeof console !== "undefined" && typeof console.error === "function") {
771
+ console.error("[Tracing] Failed to post session end:", errMsg);
638
772
  }
639
- const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
640
- const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
641
- throw new Error(`${statusLine}${bodyPreview}`);
773
+ throw err;
642
774
  }
643
775
  }
644
776
  function createEmptySummary() {
@@ -817,7 +949,8 @@ function createTraceSession(opts) {
817
949
  events: [],
818
950
  summary: createEmptySummary(),
819
951
  status: "in_progress",
820
- errors: []
952
+ errors: [],
953
+ sessionStarted: false
821
954
  };
822
955
  if (cfg.sink.type === "file") {
823
956
  const baseDir = cfg.sink.baseDir;
@@ -829,6 +962,49 @@ function createTraceSession(opts) {
829
962
  }
830
963
  return runtime;
831
964
  }
965
+ async function startStreamingSession(session, agentRuntime) {
966
+ if (!session) return;
967
+ if (session.resolvedConfig.mode !== "streaming") return;
968
+ if (session.sessionStarted) return;
969
+ const sink = session.resolvedConfig.sink;
970
+ const agentInfo = agentRuntime ? {
971
+ name: agentRuntime.name || "unknown-agent",
972
+ version: agentRuntime.version,
973
+ model: getModelName(agentRuntime.model) || "unknown-model",
974
+ provider: getProviderName(agentRuntime.model)
975
+ } : void 0;
976
+ session.agentInfo = agentInfo;
977
+ const configSnapshot = buildConfigSnapshot(session);
978
+ const startedAtIso = new Date(session.startedAt).toISOString();
979
+ const payload = {
980
+ sessionId: session.sessionId,
981
+ startedAt: startedAtIso,
982
+ agent: agentInfo,
983
+ config: configSnapshot
984
+ };
985
+ if (sink.type === "cognipeer" || sink.type === "http") {
986
+ try {
987
+ const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
988
+ await postStreamingSessionStart(sink.url, headers, payload);
989
+ session.sessionStarted = true;
990
+ } catch (err) {
991
+ const message = err instanceof Error ? err.message : String(err);
992
+ session.errors.push({
993
+ eventId: "session_start",
994
+ message,
995
+ type: "sink",
996
+ timestamp: startedAtIso
997
+ });
998
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
999
+ console.warn("[Tracing] Failed to start streaming session, falling back to batched:", message);
1000
+ }
1001
+ }
1002
+ } else if (sink.type === "custom" && typeof sink.onEvent === "function") {
1003
+ session.sessionStarted = true;
1004
+ } else if (sink.type === "file") {
1005
+ session.sessionStarted = true;
1006
+ }
1007
+ }
832
1008
  function recordTraceEvent(session, event) {
833
1009
  if (!session) return void 0;
834
1010
  const sequence = session.events.length + 1;
@@ -931,6 +1107,23 @@ function recordTraceEvent(session, event) {
931
1107
  }
932
1108
  }
933
1109
  }
1110
+ if (session.resolvedConfig.mode === "streaming" && session.sessionStarted) {
1111
+ if (sink.type === "cognipeer" || sink.type === "http") {
1112
+ const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
1113
+ postStreamingEvent(sink.url, headers, session.sessionId, record).catch((err) => {
1114
+ const errMsg = err instanceof Error ? err.message : String(err);
1115
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
1116
+ console.warn("[Tracing] Failed to send streaming event:", errMsg);
1117
+ }
1118
+ session.errors.push({
1119
+ eventId: record.id,
1120
+ message: errMsg,
1121
+ type: "sink",
1122
+ timestamp: timestampIso
1123
+ });
1124
+ });
1125
+ }
1126
+ }
934
1127
  return record;
935
1128
  }
936
1129
  async function finalizeTraceSession(session, params = {}) {
@@ -939,12 +1132,16 @@ async function finalizeTraceSession(session, params = {}) {
939
1132
  const endedAtIso = new Date(endedAtMs).toISOString();
940
1133
  const durationMs = endedAtMs - session.startedAt;
941
1134
  const startedAtIso = new Date(session.startedAt).toISOString();
942
- const agentInfo = params.agentRuntime ? {
943
- name: params.agentRuntime.name,
944
- version: params.agentRuntime.version,
945
- model: getModelName(params.agentRuntime.model),
946
- provider: getProviderName(params.agentRuntime.model)
947
- } : void 0;
1135
+ const buildAgentInfo = (runtime) => {
1136
+ if (!runtime) return void 0;
1137
+ return {
1138
+ name: runtime.name || "unknown-agent",
1139
+ version: runtime.version,
1140
+ model: getModelName(runtime.model) || "unknown-model",
1141
+ provider: getProviderName(runtime.model)
1142
+ };
1143
+ };
1144
+ const agentInfo = buildAgentInfo(params.agentRuntime);
948
1145
  if (params.error) {
949
1146
  session.errors.push({
950
1147
  eventId: "session",
@@ -961,7 +1158,7 @@ async function finalizeTraceSession(session, params = {}) {
961
1158
  startedAt: startedAtIso,
962
1159
  endedAt: endedAtIso,
963
1160
  durationMs,
964
- agent: agentInfo,
1161
+ agent: agentInfo || session.agentInfo,
965
1162
  config: configSnapshot,
966
1163
  summary: session.summary,
967
1164
  events: session.events,
@@ -971,10 +1168,22 @@ async function finalizeTraceSession(session, params = {}) {
971
1168
  const payloadForSink = buildPayload(initialStatus);
972
1169
  let sinkFailed = false;
973
1170
  const sink = session.resolvedConfig.sink;
1171
+ const isStreamingMode = session.resolvedConfig.mode === "streaming";
974
1172
  if (sink.type === "cognipeer" || sink.type === "http") {
975
1173
  try {
976
1174
  const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
977
- await postTraceSession(sink.url, headers, payloadForSink);
1175
+ if (isStreamingMode && session.sessionStarted) {
1176
+ await postStreamingSessionEnd(sink.url, headers, {
1177
+ sessionId: session.sessionId,
1178
+ endedAt: endedAtIso,
1179
+ durationMs,
1180
+ status: initialStatus,
1181
+ summary: session.summary,
1182
+ errors: session.errors
1183
+ });
1184
+ } else {
1185
+ await postTraceSession(sink.url, headers, payloadForSink);
1186
+ }
978
1187
  } catch (err) {
979
1188
  sinkFailed = true;
980
1189
  const message = err instanceof Error ? err.message : String(err);
@@ -1112,12 +1321,29 @@ function createAgentCoreNode(opts) {
1112
1321
  };
1113
1322
  const normalizeBedrockToolPairing = (input) => {
1114
1323
  const msgs = Array.isArray(input) ? [...input] : [];
1115
- for (let i = 0; i < msgs.length; i++) {
1116
- const m = msgs[i];
1324
+ const validToolCallIds = /* @__PURE__ */ new Set();
1325
+ for (const m of msgs) {
1326
+ if (m?.role === "assistant") {
1327
+ for (const tu of extractToolUses(m)) {
1328
+ if (tu.id) validToolCallIds.add(tu.id);
1329
+ }
1330
+ }
1331
+ }
1332
+ const filteredMsgs = msgs.filter((m) => {
1333
+ if (m?.role === "tool") {
1334
+ const toolCallId = m.tool_call_id;
1335
+ if (!toolCallId || !validToolCallIds.has(toolCallId)) {
1336
+ return false;
1337
+ }
1338
+ }
1339
+ return true;
1340
+ });
1341
+ for (let i = 0; i < filteredMsgs.length; i++) {
1342
+ const m = filteredMsgs[i];
1117
1343
  if (m?.role !== "assistant") continue;
1118
1344
  const toolUses = extractToolUses(m);
1119
1345
  if (toolUses.length === 0) continue;
1120
- const next = msgs[i + 1];
1346
+ const next = filteredMsgs[i + 1];
1121
1347
  const firstId = toolUses[0]?.id;
1122
1348
  const nextIsToolForFirst = !!(next && next.role === "tool" && next.tool_call_id === firstId);
1123
1349
  if (nextIsToolForFirst) continue;
@@ -1125,12 +1351,12 @@ function createAgentCoreNode(opts) {
1125
1351
  role: "tool",
1126
1352
  name: tu.name || "unknown_tool",
1127
1353
  tool_call_id: tu.id,
1128
- content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for Bedrock tool_result adjacency."
1354
+ content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for tool_result adjacency."
1129
1355
  }));
1130
- msgs.splice(i + 1, 0, ...placeholders);
1356
+ filteredMsgs.splice(i + 1, 0, ...placeholders);
1131
1357
  i += placeholders.length;
1132
1358
  }
1133
- return msgs;
1359
+ return filteredMsgs;
1134
1360
  };
1135
1361
  const debugToolPairing = (messagesToSend) => {
1136
1362
  try {
@@ -1152,11 +1378,54 @@ function createAgentCoreNode(opts) {
1152
1378
  } catch (e) {
1153
1379
  }
1154
1380
  };
1381
+ const onEvent = state.ctx?.__onEvent;
1382
+ const onStream = state.ctx?.__onStream;
1383
+ const streamingEnabled = Boolean(state.ctx?.__streaming);
1384
+ const cancellationToken = state.ctx?.__cancellationToken;
1385
+ const abortSignal = state.ctx?.__abortSignal;
1386
+ const extractText = (chunk) => {
1387
+ if (chunk == null) return "";
1388
+ if (typeof chunk === "string") return chunk;
1389
+ if (typeof chunk?.content === "string") return chunk.content;
1390
+ if (Array.isArray(chunk?.content)) {
1391
+ return chunk.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
1392
+ }
1393
+ if (typeof chunk?.text === "string") return chunk.text;
1394
+ if (typeof chunk?.delta?.content === "string") return chunk.delta.content;
1395
+ return "";
1396
+ };
1155
1397
  let response;
1156
1398
  try {
1157
1399
  const normalizedMessages = normalizeBedrockToolPairing([...state.messages]);
1158
1400
  debugToolPairing(normalizedMessages);
1159
- response = await modelWithTools.invoke(normalizedMessages);
1401
+ if (streamingEnabled && typeof modelWithTools.stream === "function") {
1402
+ let streamedText = "";
1403
+ let streamedMessage;
1404
+ for await (const chunk of modelWithTools.stream(normalizedMessages, { signal: abortSignal, cancellationToken })) {
1405
+ if (cancellationToken && cancellationToken.isCancellationRequested || abortSignal?.aborted) {
1406
+ break;
1407
+ }
1408
+ if (chunk && typeof chunk === "object" && chunk.role) {
1409
+ streamedMessage = chunk;
1410
+ }
1411
+ const text = extractText(chunk);
1412
+ if (text) {
1413
+ streamedText += text;
1414
+ onStream?.({ text });
1415
+ onEvent?.({ type: "stream", text });
1416
+ }
1417
+ }
1418
+ if (streamedMessage) {
1419
+ response = { ...streamedMessage };
1420
+ if (response.content == null || response.content === "") {
1421
+ response.content = streamedText;
1422
+ }
1423
+ } else {
1424
+ response = { role: "assistant", content: streamedText };
1425
+ }
1426
+ } else {
1427
+ response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken });
1428
+ }
1160
1429
  } catch (err) {
1161
1430
  const durationMs2 = Date.now() - start;
1162
1431
  recordTraceEvent(traceSession, {
@@ -1177,7 +1446,7 @@ function createAgentCoreNode(opts) {
1177
1446
  ...state.messages,
1178
1447
  response
1179
1448
  ];
1180
- const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.usage;
1449
+ const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
1181
1450
  const normalized = normalizeUsage(rawUsage);
1182
1451
  const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
1183
1452
  const durationMs = Date.now() - start;
@@ -1244,6 +1513,9 @@ function createToolsNode(initialTools, opts) {
1244
1513
  };
1245
1514
  const appended = [];
1246
1515
  const onEvent = state.ctx?.__onEvent;
1516
+ const onProgress = state.ctx?.__onProgress;
1517
+ const cancellationToken = state.ctx?.__cancellationToken;
1518
+ const abortSignal = state.ctx?.__abortSignal;
1247
1519
  const traceSession = state.ctx?.__traceSession;
1248
1520
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
1249
1521
  const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
@@ -1300,7 +1572,20 @@ function createToolsNode(initialTools, opts) {
1300
1572
  name: tc.name
1301
1573
  });
1302
1574
  }
1575
+ const isCancelled = () => {
1576
+ if (abortSignal?.aborted) return { cancelled: true, reason: "aborted" };
1577
+ if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
1578
+ return { cancelled: false, reason: void 0 };
1579
+ };
1303
1580
  const runOne = async (tc) => {
1581
+ const cancelState = isCancelled();
1582
+ if (cancelState.cancelled) {
1583
+ const ctx = state.ctx = state.ctx || {};
1584
+ ctx.__cancelled = { stage: "tools", reason: cancelState.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
1585
+ onEvent?.({ type: "cancelled", stage: "tools", reason: cancelState.reason });
1586
+ onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
1587
+ return { status: "error" };
1588
+ }
1304
1589
  const t = toolByName.get(tc.name);
1305
1590
  if (!t) {
1306
1591
  onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
@@ -1379,15 +1664,17 @@ function createToolsNode(initialTools, opts) {
1379
1664
  const start = Date.now();
1380
1665
  try {
1381
1666
  onEvent?.({ type: "tool_call", phase: "start", name: t.name, id: tc.id, args });
1667
+ onProgress?.({ stage: "tools", message: `Running tool ${tc.name}` });
1382
1668
  const sanitizedArgs = sanitizeTracePayload(args);
1383
1669
  const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1384
1670
  let output;
1385
1671
  const anyTool = t;
1386
- if (typeof anyTool.func === "function") output = await anyTool.func(args);
1387
- else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args);
1388
- else if (typeof anyTool.call === "function") output = await anyTool.call(args);
1389
- else if (typeof anyTool._call === "function") output = await anyTool._call(args);
1390
- else if (typeof anyTool.run === "function") output = await anyTool.run(args);
1672
+ const callOptions = { cancellationToken, signal: abortSignal };
1673
+ if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
1674
+ else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
1675
+ else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
1676
+ else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
1677
+ else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
1391
1678
  else throw new Error("Tool is not invokable");
1392
1679
  if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
1393
1680
  const newRuntime = output.__handoff.runtime;
@@ -1417,6 +1704,7 @@ function createToolsNode(initialTools, opts) {
1417
1704
  toolHistory.push({ executionId, toolName: t.name, args, output, rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
1418
1705
  appended.push({ role: "tool", content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1419
1706
  onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: output, durationMs });
1707
+ onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
1420
1708
  if (needsApproval && approvalEntry) {
1421
1709
  approvalEntry.status = "executed";
1422
1710
  approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -1466,6 +1754,7 @@ function createToolsNode(initialTools, opts) {
1466
1754
  toolHistory.push({ executionId, toolName: t.name, args, output: `Error executing tool: ${e?.message || String(e)}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
1467
1755
  appended.push({ role: "tool", content: `Error executing tool: ${e?.message || String(e)}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1468
1756
  onEvent?.({ type: "tool_call", phase: "error", name: t.name, id: tc.id, args, error: { message: e?.message || String(e) } });
1757
+ onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: e?.message || String(e) } });
1469
1758
  const sanitizedArgs = sanitizeTracePayload(args);
1470
1759
  const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1471
1760
  const toolName = t.name || tc.name;
@@ -1509,6 +1798,14 @@ function createToolsNode(initialTools, opts) {
1509
1798
  };
1510
1799
  for (const tc of planned) {
1511
1800
  if (awaitingApproval) break;
1801
+ const cancelState = isCancelled();
1802
+ if (cancelState.cancelled) {
1803
+ const ctx = state.ctx = state.ctx || {};
1804
+ ctx.__cancelled = { stage: "tools", reason: cancelState.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
1805
+ onEvent?.({ type: "cancelled", stage: "tools", reason: cancelState.reason });
1806
+ onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
1807
+ break;
1808
+ }
1512
1809
  const result = await runOne(tc);
1513
1810
  if (result?.status === "awaiting_approval") {
1514
1811
  awaitingApproval = true;
@@ -1536,7 +1833,7 @@ function createToolsNode(initialTools, opts) {
1536
1833
  // src/nodes/toolLimitFinalize.ts
1537
1834
  function createToolLimitFinalizeNode(_opts) {
1538
1835
  return async (state) => {
1539
- const notice = { role: "system", content: "Tool-call limit reached. Produce the best possible final answer using the available context and prior tool outputs. Do not call any more tools." };
1836
+ const notice = { role: "user", content: "[System Notice] Tool-call limit reached. Produce the best possible final answer using the available context and prior tool outputs. Do not call any more tools." };
1540
1837
  const ctx = { ...state.ctx || {}, __finalizedDueToToolLimit: true };
1541
1838
  return { messages: [...state.messages, notice], ctx };
1542
1839
  };
@@ -1813,12 +2110,37 @@ function createAgent(opts) {
1813
2110
  delete nextCtx.__resumeStage;
1814
2111
  state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
1815
2112
  }
1816
- const maxToolCalls = opts.limits?.maxToolCalls === void 0 ? 50 : opts.limits?.maxToolCalls;
2113
+ const mergedLimits = {
2114
+ ...opts.limits || {},
2115
+ ...config?.limits || {}
2116
+ };
2117
+ const maxToolCalls = mergedLimits?.maxToolCalls === void 0 ? 50 : mergedLimits?.maxToolCalls;
1817
2118
  const iterationLimit = maxToolCalls === Infinity ? 100 : Math.max(maxToolCalls * 3 + 10, 40);
1818
2119
  let iterations = 0;
1819
2120
  const onStateChange = config?.onStateChange;
1820
2121
  const checkpointReason = config?.checkpointReason;
1821
2122
  let pausedStage = null;
2123
+ const onProgress = state.ctx?.__onProgress;
2124
+ const isCancelled = () => {
2125
+ const ctx = state.ctx || {};
2126
+ const token = ctx.__cancellationToken;
2127
+ const signal = ctx.__abortSignal;
2128
+ const deadline = ctx.__deadline;
2129
+ if (signal?.aborted) return { cancelled: true, reason: "aborted" };
2130
+ if (token && token.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
2131
+ if (deadline && Date.now() > deadline) return { cancelled: true, reason: "timeout" };
2132
+ return { cancelled: false, reason: void 0 };
2133
+ };
2134
+ const cancelIfRequested = (stage) => {
2135
+ const result = isCancelled();
2136
+ if (!result.cancelled) return false;
2137
+ const ctx = { ...state.ctx || {} };
2138
+ ctx.__cancelled = { stage, reason: result.reason, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
2139
+ state = { ...state, ctx };
2140
+ emit?.({ type: "cancelled", stage, reason: result.reason });
2141
+ onProgress?.({ stage, message: "Cancelled", detail: { reason: result.reason } });
2142
+ return true;
2143
+ };
1822
2144
  const checkpointIfRequested = (stage) => {
1823
2145
  if (typeof onStateChange !== "function") return false;
1824
2146
  let result = false;
@@ -1841,8 +2163,10 @@ function createAgent(opts) {
1841
2163
  };
1842
2164
  while (iterations < iterationLimit) {
1843
2165
  iterations++;
2166
+ if (cancelIfRequested("loop")) break;
1844
2167
  const skippingAgent = resumeStage === "tools";
1845
2168
  if (!skippingAgent) {
2169
+ if (cancelIfRequested("before_guardrails")) break;
1846
2170
  if (checkpointIfRequested("before_guardrails")) break;
1847
2171
  const preGuardrails = getGuardrailConfig(state);
1848
2172
  if (preGuardrails.length > 0) {
@@ -1897,7 +2221,10 @@ function createAgent(opts) {
1897
2221
  break;
1898
2222
  }
1899
2223
  }
2224
+ onProgress?.({ stage: "agent", message: "Invoking model" });
2225
+ if (cancelIfRequested("before_agent")) break;
1900
2226
  state = { ...state, ...await agentCore(state) };
2227
+ onProgress?.({ stage: "agent", message: "Model response received" });
1901
2228
  if (checkpointIfRequested("after_agent")) break;
1902
2229
  const postGuardrails = getGuardrailConfig(state);
1903
2230
  if (postGuardrails.length > 0) {
@@ -1965,7 +2292,10 @@ function createAgent(opts) {
1965
2292
  continue;
1966
2293
  }
1967
2294
  if (toolCalls.length === 0) break;
2295
+ onProgress?.({ stage: "tools", message: "Running tools" });
2296
+ if (cancelIfRequested("before_tools")) break;
1968
2297
  state = { ...state, ...await toolsNode(state) };
2298
+ onProgress?.({ stage: "tools", message: "Tools finished" });
1969
2299
  if (state.ctx?.__awaitingApproval) break;
1970
2300
  if (checkpointIfRequested("after_tools")) break;
1971
2301
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
@@ -2006,15 +2336,39 @@ function createAgent(opts) {
2006
2336
  }
2007
2337
  const invokeAgent = async (input, config) => {
2008
2338
  const onEvent = config?.onEvent;
2339
+ const onProgress = config?.onProgress;
2340
+ const onStream = config?.onStream;
2341
+ const streamEnabled = config?.stream === true;
2009
2342
  const emit = (e) => {
2010
2343
  try {
2011
2344
  onEvent?.(e);
2012
2345
  } catch {
2013
2346
  }
2014
2347
  };
2348
+ const emitProgress = (progress) => {
2349
+ try {
2350
+ onProgress?.(progress);
2351
+ } catch {
2352
+ }
2353
+ emit({ type: "progress", ...progress });
2354
+ };
2015
2355
  const traceSession = createTraceSession(opts);
2016
- const ctx = { ...input.ctx || {}, __onEvent: onEvent };
2356
+ const ctx = {
2357
+ ...input.ctx || {},
2358
+ __onEvent: onEvent,
2359
+ __onProgress: emitProgress,
2360
+ __onStream: onStream,
2361
+ __streaming: streamEnabled
2362
+ };
2017
2363
  if (traceSession) ctx.__traceSession = traceSession;
2364
+ if (config?.cancellationToken) ctx.__cancellationToken = config.cancellationToken;
2365
+ if (config?.cancellationToken?.aborted !== void 0) {
2366
+ ctx.__abortSignal = config?.cancellationToken;
2367
+ }
2368
+ if (config?.timeoutMs && config.timeoutMs > 0) {
2369
+ ctx.__deadline = Date.now() + config.timeoutMs;
2370
+ }
2371
+ const runtimeWithInvokeLimits = config?.limits ? { ...runtime, limits: { ...runtime.limits || {}, ...config.limits } } : runtime;
2018
2372
  const initial = {
2019
2373
  messages: input.messages || [],
2020
2374
  toolCallCount: input.toolCallCount || 0,
@@ -2023,7 +2377,7 @@ function createAgent(opts) {
2023
2377
  metadata: input.metadata,
2024
2378
  ctx,
2025
2379
  pendingApprovals: input.pendingApprovals || [],
2026
- agent: input.agent || runtime,
2380
+ agent: input.agent || runtimeWithInvokeLimits,
2027
2381
  usage: input.usage || { perRequest: [], totals: {} }
2028
2382
  };
2029
2383
  let res;
@@ -2066,6 +2420,10 @@ function createAgent(opts) {
2066
2420
  }
2067
2421
  }
2068
2422
  emit({ type: "finalAnswer", content: typeof finalMsg?.content === "string" ? finalMsg.content : content });
2423
+ if (streamEnabled && content) {
2424
+ onStream?.({ text: content, isFinal: true });
2425
+ emit({ type: "stream", text: content, isFinal: true });
2426
+ }
2069
2427
  return {
2070
2428
  content,
2071
2429
  output: parsed,
@@ -2236,6 +2594,31 @@ function createAgentNode(opts) {
2236
2594
  // src/nodes/contextSummarize.ts
2237
2595
  var systemMessage = (content) => ({ role: "system", content });
2238
2596
  var humanMessage = (content) => ({ role: "user", content });
2597
+ function validateMessageSequence(messages) {
2598
+ const validated = [];
2599
+ const pendingToolCallIds = /* @__PURE__ */ new Set();
2600
+ for (const m of messages) {
2601
+ if (m.role === "assistant") {
2602
+ if (!m.tool_calls || m.tool_calls.length === 0) {
2603
+ pendingToolCallIds.clear();
2604
+ } else {
2605
+ m.tool_calls.forEach((tc) => {
2606
+ if (tc.id) pendingToolCallIds.add(tc.id);
2607
+ });
2608
+ }
2609
+ validated.push(m);
2610
+ } else if (m.role === "tool") {
2611
+ const toolCallId = m.tool_call_id;
2612
+ if (toolCallId && pendingToolCallIds.has(toolCallId)) {
2613
+ pendingToolCallIds.delete(toolCallId);
2614
+ validated.push(m);
2615
+ }
2616
+ } else {
2617
+ validated.push(m);
2618
+ }
2619
+ }
2620
+ return validated;
2621
+ }
2239
2622
  function getSummarizationConfig(opts) {
2240
2623
  if (typeof opts.summarization === "object") {
2241
2624
  return {
@@ -2243,13 +2626,15 @@ function getSummarizationConfig(opts) {
2243
2626
  maxTokens: opts.summarization.maxTokens ?? 5e4,
2244
2627
  // Max tokens we can send to the model for summarization prompt itself
2245
2628
  // This should be well under the model's context limit
2246
- summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3
2629
+ summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3,
2630
+ promptTemplate: opts.summarization.promptTemplate
2247
2631
  };
2248
2632
  }
2249
2633
  return {
2250
2634
  enabled: opts.summarization !== false,
2251
2635
  maxTokens: 5e4,
2252
- summaryPromptMaxTokens: 8e3
2636
+ summaryPromptMaxTokens: 8e3,
2637
+ promptTemplate: void 0
2253
2638
  };
2254
2639
  }
2255
2640
  function createContextSummarizeNode(opts) {
@@ -2258,10 +2643,14 @@ function createContextSummarizeNode(opts) {
2258
2643
  if (!config.enabled) return {};
2259
2644
  const model = opts.model;
2260
2645
  const messages = state.messages || [];
2646
+ const onEvent = state.ctx?.__onEvent;
2647
+ const traceSession = state.ctx?.__traceSession;
2261
2648
  const compressableMessages = messages.filter((m) => m.role === "tool" && m.content !== "SUMMARIZED");
2262
2649
  if (compressableMessages.length === 0) {
2263
2650
  return {};
2264
2651
  }
2652
+ 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");
2653
+ const tokenCountBefore = countApproxTokens(allTextBefore);
2265
2654
  const summaryPromptMaxTokens = config.summaryPromptMaxTokens;
2266
2655
  const availableTokensForConversation = summaryPromptMaxTokens - 500;
2267
2656
  const messageTexts = [];
@@ -2289,32 +2678,93 @@ function createContextSummarizeNode(opts) {
2289
2678
  accumulatedTokens += messageTokens;
2290
2679
  }
2291
2680
  const conversationText = messageTexts.join("\n\n");
2292
- const summaryPrompt = [
2293
- systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
2294
- humanMessage(
2295
- `Please summarize the following conversation history.
2681
+ const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
2682
+ const defaultPrompt = `Please summarize the following conversation history and update any previous summary.
2296
2683
  Focus on:
2297
2684
  - User goals and intent.
2298
2685
  - Key decisions made and actions taken.
2299
2686
  - Important tool outputs and data retrieved.
2300
2687
  - Current state of the task.
2301
2688
 
2689
+ Previous summary (if any):
2690
+ ${previousSummary || "(none)"}
2691
+
2302
2692
  Conversation:
2303
2693
  ${conversationText}
2304
2694
 
2305
- Summary:`
2306
- )
2695
+ Summary:`;
2696
+ const template = config.promptTemplate || defaultPrompt;
2697
+ const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
2698
+ const summaryPrompt = [
2699
+ systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
2700
+ humanMessage(promptBody)
2307
2701
  ];
2702
+ const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
2308
2703
  let summaryText = "Summary unavailable.";
2704
+ let outputTokensEstimate = 0;
2705
+ let durationMs = 0;
2706
+ let inputTokensActual;
2707
+ let outputTokensActual;
2708
+ let cachedInputTokens;
2709
+ let totalTokensActual;
2710
+ const startTime = Date.now();
2309
2711
  try {
2310
2712
  const response = await model.invoke(summaryPrompt);
2713
+ durationMs = Date.now() - startTime;
2311
2714
  summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2715
+ outputTokensEstimate = countApproxTokens(summaryText);
2716
+ const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2717
+ const normalized = normalizeUsage(rawUsage);
2718
+ if (normalized) {
2719
+ inputTokensActual = normalized.prompt_tokens;
2720
+ outputTokensActual = normalized.completion_tokens;
2721
+ totalTokensActual = normalized.total_tokens;
2722
+ cachedInputTokens = normalized.prompt_tokens_details?.cached_tokens;
2723
+ }
2312
2724
  } catch (err) {
2725
+ durationMs = Date.now() - startTime;
2313
2726
  console.error("[ContextSummarize] Failed to generate summary:", err);
2727
+ const errorEvent = {
2728
+ type: "summarization",
2729
+ summary: "",
2730
+ messagesCompressed: 0,
2731
+ inputTokens: inputTokensEstimate,
2732
+ outputTokens: 0,
2733
+ durationMs,
2734
+ previousSummary: previousSummary || void 0,
2735
+ tokenCountBefore,
2736
+ tokenCountAfter: tokenCountBefore
2737
+ };
2738
+ onEvent?.(errorEvent);
2739
+ recordTraceEvent(traceSession, {
2740
+ type: "summarization",
2741
+ label: "Context Summarization Failed",
2742
+ actor: { scope: "system", name: "summarizer", role: "summarization" },
2743
+ status: "error",
2744
+ durationMs,
2745
+ inputTokens: inputTokensEstimate,
2746
+ outputTokens: 0,
2747
+ error: { message: err?.message || String(err), stack: err?.stack },
2748
+ messageList: sanitizeTracePayload(summaryPrompt)
2749
+ });
2314
2750
  return {};
2315
2751
  }
2752
+ const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
2753
+ messages.forEach((m, idx) => {
2754
+ if (m.role === "assistant" && m.tool_calls && Array.isArray(m.tool_calls)) {
2755
+ m.tool_calls.forEach((tc) => {
2756
+ if (tc.id) {
2757
+ toolCallIdToAssistantIdx.set(tc.id, idx);
2758
+ }
2759
+ });
2760
+ }
2761
+ });
2316
2762
  const newMessages = messages.map((m) => {
2317
2763
  if (m.role === "tool") {
2764
+ const toolCallId = m.tool_call_id;
2765
+ if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
2766
+ return null;
2767
+ }
2318
2768
  if (m.content === "SUMMARIZED") return m;
2319
2769
  return {
2320
2770
  ...m,
@@ -2322,7 +2772,8 @@ Summary:`
2322
2772
  };
2323
2773
  }
2324
2774
  return m;
2325
- });
2775
+ }).filter((m) => m !== null);
2776
+ const validatedMessages = validateMessageSequence(newMessages);
2326
2777
  const summaryToolCallId = `call_summary_${Date.now()}`;
2327
2778
  const assistantSummaryCall = {
2328
2779
  role: "assistant",
@@ -2346,8 +2797,53 @@ Summary:`
2346
2797
  // Matches the tool name in the call
2347
2798
  content: summaryText
2348
2799
  };
2800
+ const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
2801
+ const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
2802
+ 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");
2803
+ const tokenCountAfter = countApproxTokens(allTextAfter);
2804
+ const summarizationEvent = {
2805
+ type: "summarization",
2806
+ summary: summaryText,
2807
+ messagesCompressed: compressableMessages.length,
2808
+ inputTokens: inputTokensActual ?? inputTokensEstimate,
2809
+ outputTokens: outputTokensActual ?? outputTokensEstimate,
2810
+ cachedInputTokens,
2811
+ totalTokens: totalTokensActual ?? (inputTokensActual ?? inputTokensEstimate) + (outputTokensActual ?? outputTokensEstimate),
2812
+ durationMs,
2813
+ previousSummary: previousSummary || void 0,
2814
+ tokenCountBefore,
2815
+ tokenCountAfter,
2816
+ archivedCount: compressableMessages.length
2817
+ // deprecated field for backward compatibility
2818
+ };
2819
+ onEvent?.(summarizationEvent);
2820
+ recordTraceEvent(traceSession, {
2821
+ type: "summarization",
2822
+ label: "Context Summarization",
2823
+ actor: { scope: "system", name: "summarizer", role: "summarization" },
2824
+ status: "success",
2825
+ durationMs,
2826
+ inputTokens: inputTokensActual ?? inputTokensEstimate,
2827
+ outputTokens: outputTokensActual ?? outputTokensEstimate,
2828
+ cachedInputTokens,
2829
+ totalTokens: totalTokensActual,
2830
+ messageList: sanitizeTracePayload([
2831
+ ...summaryPrompt,
2832
+ { role: "assistant", content: summaryText, name: "summarization_result" }
2833
+ ]),
2834
+ debug: {
2835
+ messagesCompressed: compressableMessages.length,
2836
+ tokenCountBefore,
2837
+ tokenCountAfter,
2838
+ tokensSaved: tokenCountBefore - tokenCountAfter,
2839
+ previousSummaryLength: previousSummary?.length || 0,
2840
+ usageFromModel: inputTokensActual !== void 0,
2841
+ cachedInputTokens
2842
+ }
2843
+ });
2349
2844
  return {
2350
- messages: [...newMessages, assistantSummaryCall, toolSummaryResponse]
2845
+ messages: finalMessages,
2846
+ summaries
2351
2847
  };
2352
2848
  };
2353
2849
  }
@@ -2452,9 +2948,29 @@ function needsSummarization(state, opts, summarizationEnabled) {
2452
2948
  maxTok = 5e4;
2453
2949
  }
2454
2950
  try {
2455
- 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");
2951
+ const allText = (state.messages || []).map((m) => {
2952
+ if (typeof m.content === "string") return m.content;
2953
+ if (Array.isArray(m.content)) {
2954
+ return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
2955
+ }
2956
+ if (m.content && typeof m.content === "object") {
2957
+ return JSON.stringify(m.content);
2958
+ }
2959
+ return "";
2960
+ }).join("\n");
2456
2961
  const tokenCount = countApproxTokens(allText);
2457
2962
  const needsSum = tokenCount > maxTok;
2963
+ if (process.env.DEBUG_SUMMARIZATION) {
2964
+ const msgCount = (state.messages || []).length;
2965
+ const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
2966
+ const toolDetails = toolMsgs.map((m, i) => {
2967
+ const contentStr = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
2968
+ return `[${i}] id:${m.tool_call_id?.slice(-8) || "none"} len:${contentStr.length} preview:"${contentStr.slice(0, 30)}..."`;
2969
+ });
2970
+ console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
2971
+ toolDetails.forEach((d) => console.log(` ${d}`));
2972
+ console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
2973
+ }
2458
2974
  return needsSum;
2459
2975
  } catch (e) {
2460
2976
  return false;
@@ -2512,7 +3028,8 @@ function createSmartAgent(opts) {
2512
3028
  const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
2513
3029
  let state = { ...input, messages: seedMessages };
2514
3030
  let lastResult = null;
2515
- const iterationLimit = Math.max((opts.limits?.maxToolCalls ?? 10) * 3 + 5, 30);
3031
+ const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? opts.limits?.maxToolCalls ?? 10;
3032
+ const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
2516
3033
  for (let i = 0; i < iterationLimit; i++) {
2517
3034
  const next = summarizationEnabled ? decideBefore(state) : "agent";
2518
3035
  if (next === "contextSummarize" && summarizer) {
@@ -2524,7 +3041,11 @@ function createSmartAgent(opts) {
2524
3041
  }
2525
3042
  const res = await base.invoke(state, config);
2526
3043
  lastResult = res;
3044
+ const currentSummaries = state.summaries;
2527
3045
  state = res.state || { ...state, messages: res.messages };
3046
+ if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
3047
+ state = { ...state, summaries: currentSummaries };
3048
+ }
2528
3049
  if (state.ctx?.__needsSummarization && summarizer) {
2529
3050
  const ctx = { ...state.ctx || {} };
2530
3051
  delete ctx.__needsSummarization;
@@ -2552,6 +3073,13 @@ function createSmartAgent(opts) {
2552
3073
  const res = await base.invoke(state, config);
2553
3074
  lastResult = res;
2554
3075
  }
3076
+ if (state.summaries && state.summaries.length > 0) {
3077
+ if (lastResult.state) {
3078
+ lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
3079
+ } else {
3080
+ lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
3081
+ }
3082
+ }
2555
3083
  return lastResult;
2556
3084
  },
2557
3085
  snapshot: base.snapshot,
@@ -2874,6 +3402,7 @@ exports.jsonSchemaRule = jsonSchemaRule;
2874
3402
  exports.regexRule = regexRule;
2875
3403
  exports.resolveToolApprovalState = resolveToolApprovalState;
2876
3404
  exports.restoreSnapshot = restoreSnapshot;
3405
+ exports.startStreamingSession = startStreamingSession;
2877
3406
  exports.withTools = withTools;
2878
3407
  //# sourceMappingURL=index.cjs.map
2879
3408
  //# sourceMappingURL=index.cjs.map