@cognipeer/agent-sdk 0.2.3 → 0.2.5

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
@@ -559,6 +559,7 @@ function ensureDir(p) {
559
559
  function withDefaults(config) {
560
560
  const enabled = Boolean(config?.enabled);
561
561
  const logData = config?.logData ?? DEFAULT_TRACE_CONFIG.logData;
562
+ const mode = config?.mode === "streaming" ? "streaming" : "batched";
562
563
  let sink;
563
564
  try {
564
565
  sink = resolveSink(config?.sink);
@@ -571,7 +572,7 @@ function withDefaults(config) {
571
572
  return {
572
573
  enabled,
573
574
  logData,
574
- mode: "batched",
575
+ mode,
575
576
  sink
576
577
  };
577
578
  }
@@ -670,6 +671,108 @@ async function postTraceSession(url, headers, payload) {
670
671
  throw err;
671
672
  }
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);
772
+ }
773
+ throw err;
774
+ }
775
+ }
673
776
  function createEmptySummary() {
674
777
  return {
675
778
  totalDurationMs: 0,
@@ -846,7 +949,8 @@ function createTraceSession(opts) {
846
949
  events: [],
847
950
  summary: createEmptySummary(),
848
951
  status: "in_progress",
849
- errors: []
952
+ errors: [],
953
+ sessionStarted: false
850
954
  };
851
955
  if (cfg.sink.type === "file") {
852
956
  const baseDir = cfg.sink.baseDir;
@@ -858,6 +962,49 @@ function createTraceSession(opts) {
858
962
  }
859
963
  return runtime;
860
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
+ }
861
1008
  function recordTraceEvent(session, event) {
862
1009
  if (!session) return void 0;
863
1010
  const sequence = session.events.length + 1;
@@ -960,6 +1107,23 @@ function recordTraceEvent(session, event) {
960
1107
  }
961
1108
  }
962
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
+ }
963
1127
  return record;
964
1128
  }
965
1129
  async function finalizeTraceSession(session, params = {}) {
@@ -994,7 +1158,7 @@ async function finalizeTraceSession(session, params = {}) {
994
1158
  startedAt: startedAtIso,
995
1159
  endedAt: endedAtIso,
996
1160
  durationMs,
997
- agent: agentInfo,
1161
+ agent: agentInfo || session.agentInfo,
998
1162
  config: configSnapshot,
999
1163
  summary: session.summary,
1000
1164
  events: session.events,
@@ -1004,10 +1168,22 @@ async function finalizeTraceSession(session, params = {}) {
1004
1168
  const payloadForSink = buildPayload(initialStatus);
1005
1169
  let sinkFailed = false;
1006
1170
  const sink = session.resolvedConfig.sink;
1171
+ const isStreamingMode = session.resolvedConfig.mode === "streaming";
1007
1172
  if (sink.type === "cognipeer" || sink.type === "http") {
1008
1173
  try {
1009
1174
  const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
1010
- 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
+ }
1011
1187
  } catch (err) {
1012
1188
  sinkFailed = true;
1013
1189
  const message = err instanceof Error ? err.message : String(err);
@@ -1220,7 +1396,95 @@ function createAgentCoreNode(opts) {
1220
1396
  };
1221
1397
  let response;
1222
1398
  try {
1223
- const normalizedMessages = normalizeBedrockToolPairing([...state.messages]);
1399
+ const ensureUniqueToolCallIds = (input) => {
1400
+ const msgs = Array.isArray(input) ? input.map((m) => ({ ...m })) : [];
1401
+ const usedIds = /* @__PURE__ */ new Set();
1402
+ let counter = 0;
1403
+ for (let i = 0; i < msgs.length; i++) {
1404
+ const m = msgs[i];
1405
+ if (m?.role !== "assistant") continue;
1406
+ const hasToolCalls = Array.isArray(m.tool_calls) && m.tool_calls.length > 0;
1407
+ const contentArr = Array.isArray(m.content) ? m.content : [];
1408
+ const hasContentToolUse = contentArr.some((c) => c?.type === "tool_use" && c?.id);
1409
+ const hasAdditionalToolCalls = Array.isArray(m.additional_kwargs?.tool_calls) && m.additional_kwargs.tool_calls.length > 0;
1410
+ if (!hasToolCalls && !hasContentToolUse && !hasAdditionalToolCalls) continue;
1411
+ if (hasToolCalls) {
1412
+ m.tool_calls = m.tool_calls.map((tc) => ({ ...tc }));
1413
+ }
1414
+ if (hasContentToolUse) {
1415
+ m.content = contentArr.map(
1416
+ (c) => c && typeof c === "object" ? { ...c } : c
1417
+ );
1418
+ }
1419
+ if (hasAdditionalToolCalls) {
1420
+ m.additional_kwargs = { ...m.additional_kwargs };
1421
+ m.additional_kwargs.tool_calls = m.additional_kwargs.tool_calls.map((tc) => ({ ...tc }));
1422
+ }
1423
+ const idsInMessage = /* @__PURE__ */ new Set();
1424
+ if (hasToolCalls) {
1425
+ for (const tc of m.tool_calls) {
1426
+ if (tc?.id) idsInMessage.add(tc.id);
1427
+ }
1428
+ }
1429
+ if (hasContentToolUse) {
1430
+ for (const c of m.content) {
1431
+ if (c?.type === "tool_use" && c?.id) idsInMessage.add(c.id);
1432
+ }
1433
+ }
1434
+ if (hasAdditionalToolCalls) {
1435
+ for (const tc of m.additional_kwargs.tool_calls) {
1436
+ if (tc?.id) idsInMessage.add(tc.id);
1437
+ }
1438
+ }
1439
+ for (const id of idsInMessage) {
1440
+ if (!usedIds.has(id)) {
1441
+ usedIds.add(id);
1442
+ continue;
1443
+ }
1444
+ const newId = `${id}_${Date.now()}_${counter++}`;
1445
+ if (hasToolCalls) {
1446
+ for (const tc of m.tool_calls) {
1447
+ if (tc.id === id) tc.id = newId;
1448
+ }
1449
+ }
1450
+ if (hasContentToolUse && Array.isArray(m.content)) {
1451
+ for (const c of m.content) {
1452
+ if (c?.type === "tool_use" && c.id === id) c.id = newId;
1453
+ }
1454
+ }
1455
+ if (hasAdditionalToolCalls) {
1456
+ for (const tc of m.additional_kwargs.tool_calls) {
1457
+ if (tc.id === id) tc.id = newId;
1458
+ }
1459
+ }
1460
+ for (let j = i + 1; j < msgs.length; j++) {
1461
+ const next = msgs[j];
1462
+ if (next?.role === "assistant") break;
1463
+ if (next?.role === "tool" && next.tool_call_id === id) {
1464
+ next.tool_call_id = newId;
1465
+ }
1466
+ if (Array.isArray(next?.content)) {
1467
+ let contentCloned = false;
1468
+ for (let k = 0; k < next.content.length; k++) {
1469
+ const block = next.content[k];
1470
+ if (block?.type === "tool_result" && block.tool_use_id === id) {
1471
+ if (!contentCloned) {
1472
+ next.content = next.content.map((x) => x && typeof x === "object" ? { ...x } : x);
1473
+ contentCloned = true;
1474
+ }
1475
+ next.content[k].tool_use_id = newId;
1476
+ }
1477
+ }
1478
+ }
1479
+ }
1480
+ usedIds.add(newId);
1481
+ }
1482
+ }
1483
+ return msgs;
1484
+ };
1485
+ const normalizedMessages = ensureUniqueToolCallIds(
1486
+ normalizeBedrockToolPairing([...state.messages])
1487
+ );
1224
1488
  debugToolPairing(normalizedMessages);
1225
1489
  if (streamingEnabled && typeof modelWithTools.stream === "function") {
1226
1490
  let streamedText = "";
@@ -2467,10 +2731,14 @@ function createContextSummarizeNode(opts) {
2467
2731
  if (!config.enabled) return {};
2468
2732
  const model = opts.model;
2469
2733
  const messages = state.messages || [];
2734
+ const onEvent = state.ctx?.__onEvent;
2735
+ const traceSession = state.ctx?.__traceSession;
2470
2736
  const compressableMessages = messages.filter((m) => m.role === "tool" && m.content !== "SUMMARIZED");
2471
2737
  if (compressableMessages.length === 0) {
2472
2738
  return {};
2473
2739
  }
2740
+ 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");
2741
+ const tokenCountBefore = countApproxTokens(allTextBefore);
2474
2742
  const summaryPromptMaxTokens = config.summaryPromptMaxTokens;
2475
2743
  const availableTokensForConversation = summaryPromptMaxTokens - 500;
2476
2744
  const messageTexts = [];
@@ -2519,12 +2787,54 @@ Summary:`;
2519
2787
  systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
2520
2788
  humanMessage(promptBody)
2521
2789
  ];
2790
+ const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
2522
2791
  let summaryText = "Summary unavailable.";
2792
+ let outputTokensEstimate = 0;
2793
+ let durationMs = 0;
2794
+ let inputTokensActual;
2795
+ let outputTokensActual;
2796
+ let cachedInputTokens;
2797
+ let totalTokensActual;
2798
+ const startTime = Date.now();
2523
2799
  try {
2524
2800
  const response = await model.invoke(summaryPrompt);
2801
+ durationMs = Date.now() - startTime;
2525
2802
  summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2803
+ outputTokensEstimate = countApproxTokens(summaryText);
2804
+ const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2805
+ const normalized = normalizeUsage(rawUsage);
2806
+ if (normalized) {
2807
+ inputTokensActual = normalized.prompt_tokens;
2808
+ outputTokensActual = normalized.completion_tokens;
2809
+ totalTokensActual = normalized.total_tokens;
2810
+ cachedInputTokens = normalized.prompt_tokens_details?.cached_tokens;
2811
+ }
2526
2812
  } catch (err) {
2813
+ durationMs = Date.now() - startTime;
2527
2814
  console.error("[ContextSummarize] Failed to generate summary:", err);
2815
+ const errorEvent = {
2816
+ type: "summarization",
2817
+ summary: "",
2818
+ messagesCompressed: 0,
2819
+ inputTokens: inputTokensEstimate,
2820
+ outputTokens: 0,
2821
+ durationMs,
2822
+ previousSummary: previousSummary || void 0,
2823
+ tokenCountBefore,
2824
+ tokenCountAfter: tokenCountBefore
2825
+ };
2826
+ onEvent?.(errorEvent);
2827
+ recordTraceEvent(traceSession, {
2828
+ type: "summarization",
2829
+ label: "Context Summarization Failed",
2830
+ actor: { scope: "system", name: "summarizer", role: "summarization" },
2831
+ status: "error",
2832
+ durationMs,
2833
+ inputTokens: inputTokensEstimate,
2834
+ outputTokens: 0,
2835
+ error: { message: err?.message || String(err), stack: err?.stack },
2836
+ messageList: sanitizeTracePayload(summaryPrompt)
2837
+ });
2528
2838
  return {};
2529
2839
  }
2530
2840
  const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
@@ -2576,8 +2886,51 @@ Summary:`;
2576
2886
  content: summaryText
2577
2887
  };
2578
2888
  const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
2889
+ const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
2890
+ 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");
2891
+ const tokenCountAfter = countApproxTokens(allTextAfter);
2892
+ const summarizationEvent = {
2893
+ type: "summarization",
2894
+ summary: summaryText,
2895
+ messagesCompressed: compressableMessages.length,
2896
+ inputTokens: inputTokensActual ?? inputTokensEstimate,
2897
+ outputTokens: outputTokensActual ?? outputTokensEstimate,
2898
+ cachedInputTokens,
2899
+ totalTokens: totalTokensActual ?? (inputTokensActual ?? inputTokensEstimate) + (outputTokensActual ?? outputTokensEstimate),
2900
+ durationMs,
2901
+ previousSummary: previousSummary || void 0,
2902
+ tokenCountBefore,
2903
+ tokenCountAfter,
2904
+ archivedCount: compressableMessages.length
2905
+ // deprecated field for backward compatibility
2906
+ };
2907
+ onEvent?.(summarizationEvent);
2908
+ recordTraceEvent(traceSession, {
2909
+ type: "summarization",
2910
+ label: "Context Summarization",
2911
+ actor: { scope: "system", name: "summarizer", role: "summarization" },
2912
+ status: "success",
2913
+ durationMs,
2914
+ inputTokens: inputTokensActual ?? inputTokensEstimate,
2915
+ outputTokens: outputTokensActual ?? outputTokensEstimate,
2916
+ cachedInputTokens,
2917
+ totalTokens: totalTokensActual,
2918
+ messageList: sanitizeTracePayload([
2919
+ ...summaryPrompt,
2920
+ { role: "assistant", content: summaryText, name: "summarization_result" }
2921
+ ]),
2922
+ debug: {
2923
+ messagesCompressed: compressableMessages.length,
2924
+ tokenCountBefore,
2925
+ tokenCountAfter,
2926
+ tokensSaved: tokenCountBefore - tokenCountAfter,
2927
+ previousSummaryLength: previousSummary?.length || 0,
2928
+ usageFromModel: inputTokensActual !== void 0,
2929
+ cachedInputTokens
2930
+ }
2931
+ });
2579
2932
  return {
2580
- messages: [...validatedMessages, assistantSummaryCall, toolSummaryResponse],
2933
+ messages: finalMessages,
2581
2934
  summaries
2582
2935
  };
2583
2936
  };
@@ -3137,6 +3490,7 @@ exports.jsonSchemaRule = jsonSchemaRule;
3137
3490
  exports.regexRule = regexRule;
3138
3491
  exports.resolveToolApprovalState = resolveToolApprovalState;
3139
3492
  exports.restoreSnapshot = restoreSnapshot;
3493
+ exports.startStreamingSession = startStreamingSession;
3140
3494
  exports.withTools = withTools;
3141
3495
  //# sourceMappingURL=index.cjs.map
3142
3496
  //# sourceMappingURL=index.cjs.map