@cognipeer/agent-sdk 0.4.4 → 0.4.6

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
@@ -2191,7 +2191,7 @@ function resolveToolResponsePolicy(toolName, output, config) {
2191
2191
  const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
2192
2192
  let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
2193
2193
  if (classification === "critical") {
2194
- retentionPolicy = byTool || "keep_structured";
2194
+ retentionPolicy = byTool || config.toolResponses.fallbackPolicy;
2195
2195
  } else if (classification === "verbose") {
2196
2196
  retentionPolicy = byTool || config.toolResponses.largeResponsePolicy;
2197
2197
  } else if (classification === "redundant") {
@@ -2897,6 +2897,21 @@ function isSyntheticSummaryMessage(message) {
2897
2897
  }
2898
2898
  return false;
2899
2899
  }
2900
+ function getMessageText(message) {
2901
+ if (typeof message?.content === "string") return message.content;
2902
+ if (Array.isArray(message?.content)) {
2903
+ return message.content.map((chunk) => typeof chunk === "string" ? chunk : chunk?.text ?? chunk?.content ?? "").join("");
2904
+ }
2905
+ return "";
2906
+ }
2907
+ function getLastAssistantMessage(messages) {
2908
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
2909
+ if (messages[index]?.role === "assistant") {
2910
+ return messages[index];
2911
+ }
2912
+ }
2913
+ return void 0;
2914
+ }
2900
2915
  function createAgent(opts) {
2901
2916
  const resolver = createResolverNode();
2902
2917
  const agentCore = createAgentCoreNode(opts);
@@ -3085,7 +3100,7 @@ function createAgent(opts) {
3085
3100
  const tokenCount = countMessagesTokens(tokCountMessages);
3086
3101
  if (tokenCount > maxTok) {
3087
3102
  const hasFreshSummary = (state.messages || []).some(
3088
- (m) => m.role === "tool" && m.content === "SUMMARIZED"
3103
+ (m) => m.role === "tool" && typeof m.content === "string" && (m.content === "SUMMARIZED" || m.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))
3089
3104
  );
3090
3105
  if (hasFreshSummary && tokenCount <= maxTok * 1.15) ; else {
3091
3106
  const ctx = { ...state.ctx || {}, __needsSummarization: true };
@@ -3200,10 +3215,25 @@ function createAgent(opts) {
3200
3215
  } catch (err) {
3201
3216
  const errMsg = err instanceof Error ? err.message : String(err);
3202
3217
  emit?.({ type: "metadata", error: `Structured output force-finalize failed: ${errMsg}` });
3218
+ throw err;
3203
3219
  }
3204
3220
  }
3205
3221
  }
3206
3222
  }
3223
+ if (state.ctx?.__needsSummarization && !opts?.summarization) {
3224
+ throw new Error(
3225
+ "Agent context exceeded the available budget before a final assistant response could be generated. Reduce tool response size or use createSmartAgent with summarization enabled."
3226
+ );
3227
+ }
3228
+ const lastAfterLoop = state.messages[state.messages.length - 1];
3229
+ if (lastAfterLoop?.role === "tool" && !pausedStage) {
3230
+ const isExpectedExit = state.ctx?.__awaitingApproval || state.ctx?.__cancelled || state.ctx?.__finalizedDueToStructuredOutput || state.ctx?.__finalizedDueToToolLimit || state.ctx?.__needsSummarization;
3231
+ if (!isExpectedExit) {
3232
+ throw new Error(
3233
+ "Agent loop terminated with a pending tool response but no subsequent model invocation. This usually indicates a model provider error or exhausted iteration budget."
3234
+ );
3235
+ }
3236
+ }
3207
3237
  if (!pausedStage && typeof onStateChange === "function" && onStateChange(state)) {
3208
3238
  checkpointIfRequested("after_loop");
3209
3239
  }
@@ -3273,10 +3303,8 @@ function createAgent(opts) {
3273
3303
  agentRuntime: res.agent || runtime,
3274
3304
  status: "success"
3275
3305
  });
3276
- const finalMsg = res.messages[res.messages.length - 1];
3277
- let content = "";
3278
- if (typeof finalMsg?.content === "string") content = finalMsg.content;
3279
- else if (Array.isArray(finalMsg?.content)) content = finalMsg.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
3306
+ const finalAssistantMsg = getLastAssistantMessage(res.messages);
3307
+ const content = getMessageText(finalAssistantMsg);
3280
3308
  let parsed = void 0;
3281
3309
  const schema = opts.outputSchema;
3282
3310
  if (schema && res.ctx?.__structuredOutputParsed) {
@@ -3297,7 +3325,7 @@ function createAgent(opts) {
3297
3325
  } catch {
3298
3326
  }
3299
3327
  }
3300
- emit({ type: "finalAnswer", content: typeof finalMsg?.content === "string" ? finalMsg.content : content });
3328
+ emit({ type: "finalAnswer", content });
3301
3329
  if (streamEnabled && content) {
3302
3330
  onStream?.({ text: content, isFinal: true });
3303
3331
  emit({ type: "stream", text: content, isFinal: true });
@@ -3567,7 +3595,7 @@ function estimateContextRotScore(state) {
3567
3595
  const summaryCount = state.summaryRecords?.length || 0;
3568
3596
  const archivedCount = state.toolHistoryArchived?.length || 0;
3569
3597
  const messageCount = state.messages?.length || 0;
3570
- const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && message.content === "SUMMARIZED").length || 0;
3598
+ const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && typeof message.content === "string" && (message.content === "SUMMARIZED" || message.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))).length || 0;
3571
3599
  const numerator = summaryCount * 2 + archivedCount + summarizedMessages;
3572
3600
  const denominator = Math.max(messageCount, 1);
3573
3601
  return Number((numerator / denominator).toFixed(3));
@@ -3684,6 +3712,69 @@ function extractCanonicalFacts(messages) {
3684
3712
  }
3685
3713
  return [...facts.values()];
3686
3714
  }
3715
+ function flattenToolMessageContent(message) {
3716
+ if (typeof message.content === "string") {
3717
+ return message.content;
3718
+ }
3719
+ if (Array.isArray(message.content)) {
3720
+ return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? String(part ?? "")).join(" ");
3721
+ }
3722
+ return "";
3723
+ }
3724
+ function isSummarizedToolPlaceholder(content) {
3725
+ return typeof content === "string" && (content === "SUMMARIZED" || content.startsWith("SUMMARIZED_TOOL_RESPONSE"));
3726
+ }
3727
+ function summarizeToolMessagePreview(message) {
3728
+ const preview = flattenToolMessageContent(message).trim();
3729
+ if (!preview) {
3730
+ return "No summary available.";
3731
+ }
3732
+ return preview.length > 240 ? `${preview.slice(0, 240)}...` : preview;
3733
+ }
3734
+ function findToolHistoryEntry(state, toolCallId, toolName) {
3735
+ const entries = [
3736
+ ...state.toolHistory || [],
3737
+ ...state.toolHistoryArchived || []
3738
+ ];
3739
+ if (toolCallId) {
3740
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
3741
+ const entry = entries[index];
3742
+ if (entry?.tool_call_id === toolCallId) {
3743
+ return entry;
3744
+ }
3745
+ }
3746
+ }
3747
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
3748
+ const entry = entries[index];
3749
+ if (entry?.toolName === toolName) {
3750
+ return entry;
3751
+ }
3752
+ }
3753
+ return void 0;
3754
+ }
3755
+ function buildSummarizedToolMessage(message, state) {
3756
+ const toolName = message.name || "unknown_tool";
3757
+ const toolCallId = message.tool_call_id;
3758
+ const historyEntry = findToolHistoryEntry(state, toolCallId, toolName);
3759
+ const executionId = historyEntry?.executionId;
3760
+ const summary = historyEntry?.summary || summarizeToolMessagePreview(message);
3761
+ const references = [
3762
+ `toolName=${toolName}`,
3763
+ toolCallId ? `toolCallId=${toolCallId}` : null,
3764
+ executionId ? `executionId=${executionId}` : null
3765
+ ].filter(Boolean).join("; ");
3766
+ let retrievalHint = "Use get_tool_response if exact details are still needed.";
3767
+ if (executionId && toolCallId) {
3768
+ retrievalHint = `Use get_tool_response with executionId "${executionId}" or tool call id "${toolCallId}" if exact details are still needed.`;
3769
+ } else if (executionId || toolCallId) {
3770
+ retrievalHint = `Use get_tool_response with executionId "${executionId || toolCallId}" if exact details are still needed.`;
3771
+ }
3772
+ return [
3773
+ `SUMMARIZED_TOOL_RESPONSE [${references}]`,
3774
+ `Summary: ${summary}`,
3775
+ retrievalHint
3776
+ ].join("\n");
3777
+ }
3687
3778
  function mergeStableFacts(summary, additionalFacts) {
3688
3779
  if (additionalFacts.length === 0) return summary;
3689
3780
  const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
@@ -3742,7 +3833,7 @@ function createContextSummarizeNode(opts) {
3742
3833
  const onEvent = state.ctx?.__onEvent;
3743
3834
  const traceSession = state.ctx?.__traceSession;
3744
3835
  const compressableMessages = messages.filter(
3745
- (m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
3836
+ (m) => m.role === "tool" && !isSummarizedToolPlaceholder(m.content) && !isSyntheticSummaryToolMessage(m)
3746
3837
  );
3747
3838
  if (compressableMessages.length === 0) {
3748
3839
  return {};
@@ -3907,10 +3998,10 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
3907
3998
  if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
3908
3999
  return null;
3909
4000
  }
3910
- if (m.content === "SUMMARIZED") return m;
4001
+ if (isSummarizedToolPlaceholder(m.content)) return m;
3911
4002
  return {
3912
4003
  ...m,
3913
- content: "SUMMARIZED"
4004
+ content: buildSummarizedToolMessage(m, state)
3914
4005
  };
3915
4006
  }
3916
4007
  return m;
@@ -4220,8 +4311,8 @@ function createContextTools(stateRef, opts) {
4220
4311
  }
4221
4312
  const getTool = createTool({
4222
4313
  name: "get_tool_response",
4223
- description: "RETRIEVE the full output of a tool execution whose response was archived or dropped. When a tool response appears as 'ARCHIVED_TOOL_RESPONSE [executionId=xxx]' or 'DROPPED_TOOL_RESPONSE [executionId=xxx]', pass that exact executionId to this tool to get the complete data. You can also pass the tool_call_id from your original tool call.",
4224
- schema: zod.z.object({ executionId: zod.z.string().describe("Tool execution id") }),
4314
+ description: "RETRIEVE the full output of a tool execution whose response was archived, dropped, or later compacted in conversation history. When a tool response appears as 'ARCHIVED_TOOL_RESPONSE [executionId=xxx]', 'DROPPED_TOOL_RESPONSE [executionId=xxx]', or 'SUMMARIZED_TOOL_RESPONSE [...]', pass the referenced executionId to this tool to get the complete data. You can also pass the original tool_call_id from the tool call.",
4315
+ schema: zod.z.object({ executionId: zod.z.string().describe("Tool execution id or original tool_call_id") }),
4225
4316
  func: async ({ executionId }) => {
4226
4317
  const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
4227
4318
  let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));