@cognipeer/agent-sdk 0.2.5 → 0.2.7

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
@@ -142,39 +142,38 @@ function withTools(model, tools) {
142
142
  }
143
143
  function fromLangchainModel(lcModel) {
144
144
  if (!lcModel) throw new Error("fromLangchainModel: model is undefined/null");
145
- const adapted = {
146
- invoke: async (messages, options) => {
147
- const normalizeContent = (content) => {
148
- if (content == null) return content;
149
- if (typeof content === "string") return content;
150
- if (Array.isArray(content)) {
151
- return content.map((part) => {
152
- if (!part || typeof part !== "object") return part;
153
- if (part.type === "text") return { type: "text", text: String(part.text ?? "") };
154
- if (part.type === "image_url") {
155
- const img = part.image_url;
156
- if (typeof img === "string") return { type: "image_url", image_url: { url: img } };
157
- if (img && typeof img === "object") {
158
- if ("url" in img) return { type: "image_url", image_url: img };
159
- if ("base64" in img) {
160
- const media = img.media_type || "image/jpeg";
161
- const dataUrl = `data:${media};base64,${img.base64}`;
162
- const detail = img.detail;
163
- return { type: "image_url", image_url: { url: dataUrl, detail } };
164
- }
165
- }
166
- return part;
145
+ const normalizeContent = (content) => {
146
+ if (content == null) return content;
147
+ if (typeof content === "string") return content;
148
+ if (Array.isArray(content)) {
149
+ return content.map((part) => {
150
+ if (!part || typeof part !== "object") return part;
151
+ if (part.type === "text") return { type: "text", text: String(part.text ?? "") };
152
+ if (part.type === "image_url") {
153
+ const img = part.image_url;
154
+ if (typeof img === "string") return { type: "image_url", image_url: { url: img } };
155
+ if (img && typeof img === "object") {
156
+ if ("url" in img) return { type: "image_url", image_url: img };
157
+ if ("base64" in img) {
158
+ const media = img.media_type || "image/jpeg";
159
+ const dataUrl = `data:${media};base64,${img.base64}`;
160
+ const detail = img.detail;
161
+ return { type: "image_url", image_url: { url: dataUrl, detail } };
167
162
  }
168
- return part;
169
- });
163
+ }
164
+ return part;
170
165
  }
171
- return content;
172
- };
173
- const toLC = (m) => {
174
- if (m._getType || m.lc_serializable) return m;
175
- const role = m.role;
176
- return { role, content: normalizeContent(m.content), name: m.name, tool_calls: m.tool_calls, tool_call_id: m.tool_call_id };
177
- };
166
+ return part;
167
+ });
168
+ }
169
+ return content;
170
+ };
171
+ const toLC = (m) => {
172
+ if (m._getType || m.lc_serializable) return m;
173
+ return { role: m.role, content: normalizeContent(m.content), name: m.name, tool_calls: m.tool_calls, tool_call_id: m.tool_call_id };
174
+ };
175
+ const adapted = {
176
+ invoke: async (messages, options) => {
178
177
  const lcMessages = messages.map(toLC);
179
178
  const response = await lcModel.invoke(lcMessages, options);
180
179
  if (response && typeof response === "object") {
@@ -192,38 +191,10 @@ function fromLangchainModel(lcModel) {
192
191
  },
193
192
  stream: async function* (messages, options) {
194
193
  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
194
  const lcMessages = messages.map(toLC);
226
- for await (const chunk of lcModel.stream(lcMessages, options)) {
195
+ const streamResult = lcModel.stream(lcMessages, options);
196
+ const stream = typeof streamResult?.then === "function" ? await streamResult : streamResult;
197
+ for await (const chunk of stream) {
227
198
  yield chunk;
228
199
  }
229
200
  },
@@ -345,17 +316,17 @@ function normalizeUsage(raw) {
345
316
  raw.totalTokens,
346
317
  (typeof prompt === "number" ? prompt : 0) + (typeof completion === "number" ? completion : 0)
347
318
  );
348
- const promptDetailsSrc = raw.prompt_tokens_details || raw.promptTokensDetails || {};
349
- const completionDetailsSrc = raw.completion_tokens_details || raw.completionTokensDetails || {};
319
+ const promptDetailsSrc = raw.prompt_tokens_details || raw.promptTokensDetails || raw.input_token_details || raw.inputTokenDetails || {};
320
+ const completionDetailsSrc = raw.completion_tokens_details || raw.completionTokensDetails || raw.output_token_details || raw.outputTokenDetails || {};
350
321
  const normalized = {
351
322
  prompt_tokens: safe(prompt),
352
323
  completion_tokens: safe(completion),
353
324
  total_tokens: safe(total || (prompt || 0) + (completion || 0)),
354
325
  prompt_tokens_details: {
355
326
  cached_tokens: safe(
356
- promptDetailsSrc.cached_tokens || promptDetailsSrc.cached || raw.cached_input_tokens || raw.cached_prompt_tokens
327
+ promptDetailsSrc.cached_tokens || promptDetailsSrc.cached || promptDetailsSrc.cache_read || raw.cached_input_tokens || raw.cached_prompt_tokens || raw.cache_read_input_tokens
357
328
  ),
358
- audio_tokens: safe(promptDetailsSrc.audio_tokens)
329
+ audio_tokens: safe(promptDetailsSrc.audio_tokens || promptDetailsSrc.audio)
359
330
  },
360
331
  completion_tokens_details: {
361
332
  reasoning_tokens: safe(completionDetailsSrc.reasoning_tokens),
@@ -1041,8 +1012,12 @@ function recordTraceEvent(session, event) {
1041
1012
  session.summary.eventCounts[event.type] = (session.summary.eventCounts[event.type] || 0) + 1;
1042
1013
  let sections;
1043
1014
  if (session.resolvedConfig.logData) {
1044
- const converted = convertMessageListToSections(event.messageList);
1045
- sections = ensureUniqueSections(id, converted);
1015
+ if (event.sections && event.sections.length > 0) {
1016
+ sections = ensureUniqueSections(id, event.sections);
1017
+ } else {
1018
+ const converted = convertMessageListToSections(event.messageList);
1019
+ sections = ensureUniqueSections(id, converted);
1020
+ }
1046
1021
  }
1047
1022
  const resolvedModel = event.model ?? inferModelFromMessages(event.messageList);
1048
1023
  const resolvedProvider = event.provider ?? inferProviderFromMessages(event.messageList);
@@ -1305,7 +1280,18 @@ function createAgentCoreNode(opts) {
1305
1280
  outputSchema: opts.outputSchema,
1306
1281
  tracing: opts.tracing
1307
1282
  };
1308
- const tools = runtime.tools ?? [];
1283
+ const rawTools = runtime.tools ?? [];
1284
+ const seenNames = /* @__PURE__ */ new Map();
1285
+ const tools = [];
1286
+ for (const t of rawTools) {
1287
+ const tName = t.name ?? t.schema?.title;
1288
+ if (tName && seenNames.has(tName)) {
1289
+ tools[seenNames.get(tName)] = t;
1290
+ } else {
1291
+ if (tName) seenNames.set(tName, tools.length);
1292
+ tools.push(t);
1293
+ }
1294
+ }
1309
1295
  const modelWithTools = runtime.model?.bindTools ? runtime.model.bindTools(tools) : runtime.model;
1310
1296
  const traceSession = state.ctx?.__traceSession;
1311
1297
  const actorName = runtime.name ?? opts.name ?? "agent";
@@ -1358,26 +1344,6 @@ function createAgentCoreNode(opts) {
1358
1344
  }
1359
1345
  return filteredMsgs;
1360
1346
  };
1361
- const debugToolPairing = (messagesToSend) => {
1362
- try {
1363
- const msgs = Array.isArray(messagesToSend) ? messagesToSend : [];
1364
- const toolUseIds = [];
1365
- const missingResults = [];
1366
- for (let i = 0; i < msgs.length; i++) {
1367
- const m = msgs[i];
1368
- if (m?.role === "assistant") {
1369
- for (const tu of extractToolUses(m)) {
1370
- toolUseIds.push(tu.id);
1371
- const next = msgs[i + 1];
1372
- if (!(next && next.role === "tool" && (next.tool_call_id === tu.id || next.tool_call_id === m?.tool_call_id))) {
1373
- missingResults.push({ id: tu.id, atIndex: i, tool: tu.name });
1374
- }
1375
- }
1376
- }
1377
- }
1378
- } catch (e) {
1379
- }
1380
- };
1381
1347
  const onEvent = state.ctx?.__onEvent;
1382
1348
  const onStream = state.ctx?.__onStream;
1383
1349
  const streamingEnabled = Boolean(state.ctx?.__streaming);
@@ -1408,25 +1374,54 @@ function createAgentCoreNode(opts) {
1408
1374
  const hasContentToolUse = contentArr.some((c) => c?.type === "tool_use" && c?.id);
1409
1375
  const hasAdditionalToolCalls = Array.isArray(m.additional_kwargs?.tool_calls) && m.additional_kwargs.tool_calls.length > 0;
1410
1376
  if (!hasToolCalls && !hasContentToolUse && !hasAdditionalToolCalls) continue;
1411
- if (hasToolCalls) {
1412
- m.tool_calls = m.tool_calls.map((tc) => ({ ...tc }));
1377
+ if (hasToolCalls && hasContentToolUse) {
1378
+ const toolCallIds = new Set(
1379
+ m.tool_calls.filter((tc) => tc?.id).map((tc) => tc.id)
1380
+ );
1381
+ const filtered = contentArr.filter((c) => {
1382
+ if (c?.type === "tool_use" && c?.id && toolCallIds.has(c.id)) return false;
1383
+ return true;
1384
+ });
1385
+ if (filtered.length === 0) {
1386
+ m.content = "";
1387
+ } else if (filtered.length !== contentArr.length) {
1388
+ m.content = filtered.length === 1 && filtered[0]?.type === "text" ? filtered[0].text ?? "" : filtered;
1389
+ }
1413
1390
  }
1414
- if (hasContentToolUse) {
1415
- m.content = contentArr.map(
1416
- (c) => c && typeof c === "object" ? { ...c } : c
1391
+ if (hasAdditionalToolCalls && Array.isArray(m.content)) {
1392
+ const akIds = new Set(
1393
+ m.additional_kwargs.tool_calls.filter((tc) => tc?.id).map((tc) => tc.id)
1417
1394
  );
1395
+ const arr = m.content;
1396
+ const filtered = arr.filter((c) => {
1397
+ if (c?.type === "tool_use" && c?.id && akIds.has(c.id)) return false;
1398
+ return true;
1399
+ });
1400
+ if (filtered.length !== arr.length) {
1401
+ m.content = filtered.length === 0 ? "" : filtered.length === 1 && filtered[0]?.type === "text" ? filtered[0].text ?? "" : filtered;
1402
+ }
1403
+ }
1404
+ if (hasToolCalls) {
1405
+ m.tool_calls = m.tool_calls.map((tc) => ({ ...tc }));
1418
1406
  }
1419
1407
  if (hasAdditionalToolCalls) {
1420
1408
  m.additional_kwargs = { ...m.additional_kwargs };
1421
1409
  m.additional_kwargs.tool_calls = m.additional_kwargs.tool_calls.map((tc) => ({ ...tc }));
1422
1410
  }
1411
+ const remainingContentArr = Array.isArray(m.content) ? m.content : [];
1412
+ const hasRemainingContentToolUse = remainingContentArr.some((c) => c?.type === "tool_use" && c?.id);
1413
+ if (hasRemainingContentToolUse) {
1414
+ m.content = remainingContentArr.map(
1415
+ (c) => c && typeof c === "object" ? { ...c } : c
1416
+ );
1417
+ }
1423
1418
  const idsInMessage = /* @__PURE__ */ new Set();
1424
1419
  if (hasToolCalls) {
1425
1420
  for (const tc of m.tool_calls) {
1426
1421
  if (tc?.id) idsInMessage.add(tc.id);
1427
1422
  }
1428
1423
  }
1429
- if (hasContentToolUse) {
1424
+ if (hasRemainingContentToolUse) {
1430
1425
  for (const c of m.content) {
1431
1426
  if (c?.type === "tool_use" && c?.id) idsInMessage.add(c.id);
1432
1427
  }
@@ -1447,7 +1442,7 @@ function createAgentCoreNode(opts) {
1447
1442
  if (tc.id === id) tc.id = newId;
1448
1443
  }
1449
1444
  }
1450
- if (hasContentToolUse && Array.isArray(m.content)) {
1445
+ if (hasRemainingContentToolUse && Array.isArray(m.content)) {
1451
1446
  for (const c of m.content) {
1452
1447
  if (c?.type === "tool_use" && c.id === id) c.id = newId;
1453
1448
  }
@@ -1485,11 +1480,12 @@ function createAgentCoreNode(opts) {
1485
1480
  const normalizedMessages = ensureUniqueToolCallIds(
1486
1481
  normalizeBedrockToolPairing([...state.messages])
1487
1482
  );
1488
- debugToolPairing(normalizedMessages);
1489
1483
  if (streamingEnabled && typeof modelWithTools.stream === "function") {
1490
1484
  let streamedText = "";
1491
1485
  let streamedMessage;
1492
- for await (const chunk of modelWithTools.stream(normalizedMessages, { signal: abortSignal, cancellationToken })) {
1486
+ const streamResult = modelWithTools.stream(normalizedMessages, { signal: abortSignal, cancellationToken });
1487
+ const stream = typeof streamResult?.then === "function" ? await streamResult : streamResult;
1488
+ for await (const chunk of stream) {
1493
1489
  if (cancellationToken && cancellationToken.isCancellationRequested || abortSignal?.aborted) {
1494
1490
  break;
1495
1491
  }
@@ -1534,7 +1530,7 @@ function createAgentCoreNode(opts) {
1534
1530
  ...state.messages,
1535
1531
  response
1536
1532
  ];
1537
- const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
1533
+ const rawUsage = response?.usage_metadata || response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
1538
1534
  const normalized = normalizeUsage(rawUsage);
1539
1535
  const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
1540
1536
  const durationMs = Date.now() - start;
@@ -1625,41 +1621,6 @@ function createToolsNode(initialTools, opts) {
1625
1621
  const remaining = Math.max(0, limits.maxToolCalls - toolCount);
1626
1622
  const planned = toolCalls.slice(0, remaining);
1627
1623
  const skipped = toolCalls.slice(remaining);
1628
- for (const tc of skipped) {
1629
- onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
1630
- const sanitizedArgs = sanitizeTracePayload(tc.args);
1631
- const messageList = [
1632
- {
1633
- role: "assistant",
1634
- name: tc.name,
1635
- content: `Skipped tool due to tool-call limit: ${tc.name}`,
1636
- tool_calls: [
1637
- {
1638
- id: tc.id,
1639
- type: "function",
1640
- function: {
1641
- name: tc.name,
1642
- arguments: sanitizedArgs
1643
- }
1644
- }
1645
- ]
1646
- }
1647
- ];
1648
- recordTraceEvent(traceSession, {
1649
- type: "tool_call",
1650
- label: `Tool Skipped - ${tc.name}`,
1651
- actor: { scope: "tool", name: tc.name, role: "tool" },
1652
- status: "skipped",
1653
- toolExecutionId: tc.id,
1654
- messageList
1655
- });
1656
- appended.push({
1657
- role: "tool",
1658
- content: `Skipped tool due to tool-call limit: ${tc.name}`,
1659
- tool_call_id: tc.id || `${tc.name}_${appended.length}`,
1660
- name: tc.name
1661
- });
1662
- }
1663
1624
  const isCancelled = () => {
1664
1625
  if (abortSignal?.aborted) return { cancelled: true, reason: "aborted" };
1665
1626
  if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
@@ -1900,6 +1861,42 @@ function createToolsNode(initialTools, opts) {
1900
1861
  break;
1901
1862
  }
1902
1863
  }
1864
+ for (const tc of skipped) {
1865
+ onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
1866
+ const sanitizedArgs = sanitizeTracePayload(tc.args);
1867
+ const messageList = [
1868
+ {
1869
+ role: "assistant",
1870
+ name: tc.name,
1871
+ content: `Skipped tool due to tool-call limit: ${tc.name}`,
1872
+ tool_calls: [
1873
+ {
1874
+ id: tc.id,
1875
+ type: "function",
1876
+ function: {
1877
+ name: tc.name,
1878
+ arguments: sanitizedArgs
1879
+ }
1880
+ }
1881
+ ]
1882
+ }
1883
+ ];
1884
+ recordTraceEvent(traceSession, {
1885
+ type: "tool_call",
1886
+ label: `Tool Skipped - ${tc.name}`,
1887
+ actor: { scope: "tool", name: tc.name, role: "tool" },
1888
+ status: "skipped",
1889
+ toolExecutionId: tc.id,
1890
+ messageList
1891
+ });
1892
+ appended.push({
1893
+ role: "tool",
1894
+ content: `Skipped tool due to tool-call limit: ${tc.name}`,
1895
+ tool_call_id: tc.id || `${tc.name}_${appended.length}`,
1896
+ name: tc.name
1897
+ });
1898
+ toolCount += 1;
1899
+ }
1903
1900
  if (!awaitingApproval && state.ctx?.__awaitingApproval) {
1904
1901
  const ctx = { ...state.ctx };
1905
1902
  delete ctx.__awaitingApproval;
@@ -2131,7 +2128,62 @@ function resolveToolApprovalState(state, resolution) {
2131
2128
  // src/utils/utilTokens.ts
2132
2129
  function countApproxTokens(text) {
2133
2130
  if (!text) return 0;
2134
- return Math.ceil(text.length / 4);
2131
+ const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
2132
+ const asciiCount = text.length - nonAsciiCount;
2133
+ return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
2134
+ }
2135
+ function extractMessageText(m) {
2136
+ if (!m) return "";
2137
+ const parts = [];
2138
+ parts.push(m.role || "");
2139
+ if (m.name) parts.push(m.name);
2140
+ if (typeof m.content === "string") {
2141
+ parts.push(m.content);
2142
+ } else if (Array.isArray(m.content)) {
2143
+ for (const c of m.content) {
2144
+ if (typeof c === "string") parts.push(c);
2145
+ else if (c?.text) parts.push(c.text);
2146
+ else if (c?.content) parts.push(c.content);
2147
+ else if (c?.type === "tool_use" && c?.input) {
2148
+ parts.push(c.name || "");
2149
+ parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
2150
+ } else if (c != null && typeof c === "object") {
2151
+ parts.push(JSON.stringify(c));
2152
+ }
2153
+ }
2154
+ } else if (m.content != null && typeof m.content === "object") {
2155
+ parts.push(JSON.stringify(m.content));
2156
+ }
2157
+ if (Array.isArray(m.tool_calls)) {
2158
+ for (const tc of m.tool_calls) {
2159
+ if (tc?.function?.name) parts.push(tc.function.name);
2160
+ if (tc?.function?.arguments) {
2161
+ parts.push(
2162
+ typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2163
+ );
2164
+ }
2165
+ if (tc?.name && !tc?.function?.name) parts.push(tc.name);
2166
+ if (tc?.args && !tc?.function?.arguments) {
2167
+ parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
2168
+ }
2169
+ }
2170
+ }
2171
+ if (Array.isArray(m.additional_kwargs?.tool_calls)) {
2172
+ for (const tc of m.additional_kwargs.tool_calls) {
2173
+ if (tc?.function?.name) parts.push(tc.function.name);
2174
+ if (tc?.function?.arguments) {
2175
+ parts.push(
2176
+ typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2177
+ );
2178
+ }
2179
+ }
2180
+ }
2181
+ return parts.join("\n");
2182
+ }
2183
+ function countMessagesTokens(messages) {
2184
+ if (!Array.isArray(messages) || messages.length === 0) return 0;
2185
+ const allText = messages.map(extractMessageText).join("\n");
2186
+ return countApproxTokens(allText) + messages.length * 4;
2135
2187
  }
2136
2188
 
2137
2189
  // src/agent.ts
@@ -2234,7 +2286,8 @@ function createAgent(opts) {
2234
2286
  let result = false;
2235
2287
  try {
2236
2288
  result = onStateChange(state);
2237
- } catch {
2289
+ } catch (err) {
2290
+ console.warn("[agent-sdk] onStateChange callback error:", err);
2238
2291
  result = false;
2239
2292
  }
2240
2293
  if (!result) return false;
@@ -2301,8 +2354,7 @@ function createAgent(opts) {
2301
2354
  maxTok = 5e4;
2302
2355
  }
2303
2356
  if (maxTok) {
2304
- 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");
2305
- const tokenCount = countApproxTokens(allText);
2357
+ const tokenCount = countMessagesTokens(state.messages || []);
2306
2358
  if (tokenCount > maxTok) {
2307
2359
  const ctx = { ...state.ctx || {}, __needsSummarization: true };
2308
2360
  state = { ...state, ctx };
@@ -2412,7 +2464,9 @@ function createAgent(opts) {
2412
2464
  if (toolCallsAfter.length > 0) {
2413
2465
  state = { ...state, ...await toolsNode(state) };
2414
2466
  }
2415
- } catch {
2467
+ } catch (err) {
2468
+ const errMsg = err instanceof Error ? err.message : String(err);
2469
+ emit?.({ type: "metadata", error: `Structured output force-finalize failed: ${errMsg}` });
2416
2470
  }
2417
2471
  }
2418
2472
  }
@@ -2430,13 +2484,15 @@ function createAgent(opts) {
2430
2484
  const emit = (e) => {
2431
2485
  try {
2432
2486
  onEvent?.(e);
2433
- } catch {
2487
+ } catch (err) {
2488
+ console.warn("[agent-sdk] onEvent callback error:", err);
2434
2489
  }
2435
2490
  };
2436
2491
  const emitProgress = (progress) => {
2437
2492
  try {
2438
2493
  onProgress?.(progress);
2439
- } catch {
2494
+ } catch (err) {
2495
+ console.warn("[agent-sdk] onProgress callback error:", err);
2440
2496
  }
2441
2497
  emit({ type: "progress", ...progress });
2442
2498
  };
@@ -2679,6 +2735,83 @@ function createAgentNode(opts) {
2679
2735
  };
2680
2736
  }
2681
2737
 
2738
+ // src/utils/traceSections.ts
2739
+ function formatMessageContent(content) {
2740
+ if (content == null) return "";
2741
+ if (typeof content === "string") return content;
2742
+ if (Array.isArray(content)) {
2743
+ return content.map((item) => {
2744
+ if (typeof item === "string") return item;
2745
+ if (item && typeof item === "object") {
2746
+ if (typeof item.text === "string") return item.text;
2747
+ if (typeof item.content === "string") return item.content;
2748
+ try {
2749
+ return JSON.stringify(item);
2750
+ } catch {
2751
+ return String(item);
2752
+ }
2753
+ }
2754
+ try {
2755
+ return JSON.stringify(item);
2756
+ } catch {
2757
+ return String(item);
2758
+ }
2759
+ }).filter(Boolean).join("\n");
2760
+ }
2761
+ if (typeof content === "object") {
2762
+ if (typeof content.text === "string") return content.text;
2763
+ if (typeof content.content === "string") return content.content;
2764
+ try {
2765
+ return JSON.stringify(content, null, 2);
2766
+ } catch {
2767
+ return String(content);
2768
+ }
2769
+ }
2770
+ return String(content);
2771
+ }
2772
+ function buildSummarizationSections(opts) {
2773
+ const sections = [];
2774
+ sections.push({
2775
+ kind: "summary",
2776
+ label: "Context Summary",
2777
+ content: opts.summaryText
2778
+ });
2779
+ const tokensSaved = opts.tokenCountBefore - opts.tokenCountAfter;
2780
+ const reductionPct = opts.tokenCountBefore > 0 ? Math.round(tokensSaved / opts.tokenCountBefore * 100) : 0;
2781
+ const metaData = {
2782
+ messagesCompressed: opts.messagesCompressed,
2783
+ tokenCountBefore: opts.tokenCountBefore,
2784
+ tokenCountAfter: opts.tokenCountAfter,
2785
+ tokensSaved,
2786
+ reductionPercent: reductionPct
2787
+ };
2788
+ if (opts.inputTokens !== void 0) metaData.inputTokens = opts.inputTokens;
2789
+ if (opts.outputTokens !== void 0) metaData.outputTokens = opts.outputTokens;
2790
+ if (opts.cachedInputTokens !== void 0) metaData.cachedInputTokens = opts.cachedInputTokens;
2791
+ if (opts.durationMs !== void 0) metaData.durationMs = opts.durationMs;
2792
+ if (opts.previousSummary) metaData.previousSummaryLength = opts.previousSummary.length;
2793
+ sections.push({
2794
+ kind: "metadata",
2795
+ label: "Summarization Stats",
2796
+ data: metaData
2797
+ });
2798
+ if (Array.isArray(opts.promptMessages) && opts.promptMessages.length > 0) {
2799
+ for (const msg of opts.promptMessages) {
2800
+ const role = typeof msg?.role === "string" ? msg.role : "system";
2801
+ const content = formatMessageContent(msg?.content ?? "");
2802
+ if (content) {
2803
+ sections.push({
2804
+ kind: "message",
2805
+ label: `Summarization Prompt (${role})`,
2806
+ role,
2807
+ content
2808
+ });
2809
+ }
2810
+ }
2811
+ }
2812
+ return sections;
2813
+ }
2814
+
2682
2815
  // src/nodes/contextSummarize.ts
2683
2816
  var systemMessage = (content) => ({ role: "system", content });
2684
2817
  var humanMessage = (content) => ({ role: "user", content });
@@ -2737,8 +2870,7 @@ function createContextSummarizeNode(opts) {
2737
2870
  if (compressableMessages.length === 0) {
2738
2871
  return {};
2739
2872
  }
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);
2873
+ const tokenCountBefore = countMessagesTokens(messages);
2742
2874
  const summaryPromptMaxTokens = config.summaryPromptMaxTokens;
2743
2875
  const availableTokensForConversation = summaryPromptMaxTokens - 500;
2744
2876
  const messageTexts = [];
@@ -2801,7 +2933,7 @@ Summary:`;
2801
2933
  durationMs = Date.now() - startTime;
2802
2934
  summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2803
2935
  outputTokensEstimate = countApproxTokens(summaryText);
2804
- const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2936
+ const rawUsage = response?.usage_metadata || response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2805
2937
  const normalized = normalizeUsage(rawUsage);
2806
2938
  if (normalized) {
2807
2939
  inputTokensActual = normalized.prompt_tokens;
@@ -2833,7 +2965,16 @@ Summary:`;
2833
2965
  inputTokens: inputTokensEstimate,
2834
2966
  outputTokens: 0,
2835
2967
  error: { message: err?.message || String(err), stack: err?.stack },
2836
- messageList: sanitizeTracePayload(summaryPrompt)
2968
+ sections: buildSummarizationSections({
2969
+ summaryText: "",
2970
+ messagesCompressed: 0,
2971
+ tokenCountBefore,
2972
+ tokenCountAfter: tokenCountBefore,
2973
+ inputTokens: inputTokensEstimate,
2974
+ outputTokens: 0,
2975
+ durationMs,
2976
+ promptMessages: summaryPrompt
2977
+ })
2837
2978
  });
2838
2979
  return {};
2839
2980
  }
@@ -2887,8 +3028,7 @@ Summary:`;
2887
3028
  };
2888
3029
  const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
2889
3030
  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);
3031
+ const tokenCountAfter = countMessagesTokens(finalMessages);
2892
3032
  const summarizationEvent = {
2893
3033
  type: "summarization",
2894
3034
  summary: summaryText,
@@ -2915,10 +3055,21 @@ Summary:`;
2915
3055
  outputTokens: outputTokensActual ?? outputTokensEstimate,
2916
3056
  cachedInputTokens,
2917
3057
  totalTokens: totalTokensActual,
2918
- messageList: sanitizeTracePayload([
2919
- ...summaryPrompt,
2920
- { role: "assistant", content: summaryText, name: "summarization_result" }
2921
- ]),
3058
+ sections: buildSummarizationSections({
3059
+ summaryText,
3060
+ previousSummary: previousSummary || void 0,
3061
+ messagesCompressed: compressableMessages.length,
3062
+ tokenCountBefore,
3063
+ tokenCountAfter,
3064
+ inputTokens: inputTokensActual ?? inputTokensEstimate,
3065
+ outputTokens: outputTokensActual ?? outputTokensEstimate,
3066
+ cachedInputTokens,
3067
+ durationMs,
3068
+ promptMessages: sanitizeTracePayload([
3069
+ ...summaryPrompt,
3070
+ { role: "assistant", content: summaryText, name: "summarization_result" }
3071
+ ])
3072
+ }),
2922
3073
  debug: {
2923
3074
  messagesCompressed: compressableMessages.length,
2924
3075
  tokenCountBefore,
@@ -2946,6 +3097,12 @@ async function applyTokenLimits({
2946
3097
  let total = messages.reduce((acc, m) => acc + countApproxTokens(String(m.content || "")), 0);
2947
3098
  if (total <= budget) return state;
2948
3099
  const newMessages = [...messages];
3100
+ while (total > budget && newMessages.length > 1) {
3101
+ const idx = newMessages.findIndex((m) => m.role !== "system");
3102
+ if (idx === -1) break;
3103
+ const removed = newMessages.splice(idx, 1)[0];
3104
+ total -= countApproxTokens(String(removed.content || ""));
3105
+ }
2949
3106
  return { ...state, messages: newMessages };
2950
3107
  }
2951
3108
  function createContextTools(stateRef, opts) {
@@ -3036,30 +3193,8 @@ function needsSummarization(state, opts, summarizationEnabled) {
3036
3193
  maxTok = 5e4;
3037
3194
  }
3038
3195
  try {
3039
- const allText = (state.messages || []).map((m) => {
3040
- if (typeof m.content === "string") return m.content;
3041
- if (Array.isArray(m.content)) {
3042
- return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
3043
- }
3044
- if (m.content && typeof m.content === "object") {
3045
- return JSON.stringify(m.content);
3046
- }
3047
- return "";
3048
- }).join("\n");
3049
- const tokenCount = countApproxTokens(allText);
3050
- const needsSum = tokenCount > maxTok;
3051
- if (process.env.DEBUG_SUMMARIZATION) {
3052
- const msgCount = (state.messages || []).length;
3053
- const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
3054
- const toolDetails = toolMsgs.map((m, i) => {
3055
- const contentStr = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
3056
- return `[${i}] id:${m.tool_call_id?.slice(-8) || "none"} len:${contentStr.length} preview:"${contentStr.slice(0, 30)}..."`;
3057
- });
3058
- console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
3059
- toolDetails.forEach((d) => console.log(` ${d}`));
3060
- console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
3061
- }
3062
- return needsSum;
3196
+ const tokenCount = countMessagesTokens(state.messages || []);
3197
+ return tokenCount > maxTok;
3063
3198
  } catch (e) {
3064
3199
  return false;
3065
3200
  }
@@ -3465,6 +3600,7 @@ exports.captureSnapshot = captureSnapshot;
3465
3600
  exports.codePresenceRule = codePresenceRule;
3466
3601
  exports.cognipeerSink = cognipeerSink;
3467
3602
  exports.countApproxTokens = countApproxTokens;
3603
+ exports.countMessagesTokens = countMessagesTokens;
3468
3604
  exports.createAgent = createAgent;
3469
3605
  exports.createAgentNode = createAgentNode;
3470
3606
  exports.createCodeGuardrail = createCodeGuardrail;