@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.mjs CHANGED
@@ -2181,7 +2181,7 @@ function resolveToolResponsePolicy(toolName, output, config) {
2181
2181
  const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
2182
2182
  let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
2183
2183
  if (classification === "critical") {
2184
- retentionPolicy = byTool || "keep_structured";
2184
+ retentionPolicy = byTool || config.toolResponses.fallbackPolicy;
2185
2185
  } else if (classification === "verbose") {
2186
2186
  retentionPolicy = byTool || config.toolResponses.largeResponsePolicy;
2187
2187
  } else if (classification === "redundant") {
@@ -2887,6 +2887,21 @@ function isSyntheticSummaryMessage(message) {
2887
2887
  }
2888
2888
  return false;
2889
2889
  }
2890
+ function getMessageText(message) {
2891
+ if (typeof message?.content === "string") return message.content;
2892
+ if (Array.isArray(message?.content)) {
2893
+ return message.content.map((chunk) => typeof chunk === "string" ? chunk : chunk?.text ?? chunk?.content ?? "").join("");
2894
+ }
2895
+ return "";
2896
+ }
2897
+ function getLastAssistantMessage(messages) {
2898
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
2899
+ if (messages[index]?.role === "assistant") {
2900
+ return messages[index];
2901
+ }
2902
+ }
2903
+ return void 0;
2904
+ }
2890
2905
  function createAgent(opts) {
2891
2906
  const resolver = createResolverNode();
2892
2907
  const agentCore = createAgentCoreNode(opts);
@@ -3075,7 +3090,7 @@ function createAgent(opts) {
3075
3090
  const tokenCount = countMessagesTokens(tokCountMessages);
3076
3091
  if (tokenCount > maxTok) {
3077
3092
  const hasFreshSummary = (state.messages || []).some(
3078
- (m) => m.role === "tool" && m.content === "SUMMARIZED"
3093
+ (m) => m.role === "tool" && typeof m.content === "string" && (m.content === "SUMMARIZED" || m.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))
3079
3094
  );
3080
3095
  if (hasFreshSummary && tokenCount <= maxTok * 1.15) ; else {
3081
3096
  const ctx = { ...state.ctx || {}, __needsSummarization: true };
@@ -3190,10 +3205,25 @@ function createAgent(opts) {
3190
3205
  } catch (err) {
3191
3206
  const errMsg = err instanceof Error ? err.message : String(err);
3192
3207
  emit?.({ type: "metadata", error: `Structured output force-finalize failed: ${errMsg}` });
3208
+ throw err;
3193
3209
  }
3194
3210
  }
3195
3211
  }
3196
3212
  }
3213
+ if (state.ctx?.__needsSummarization && !opts?.summarization) {
3214
+ throw new Error(
3215
+ "Agent context exceeded the available budget before a final assistant response could be generated. Reduce tool response size or use createSmartAgent with summarization enabled."
3216
+ );
3217
+ }
3218
+ const lastAfterLoop = state.messages[state.messages.length - 1];
3219
+ if (lastAfterLoop?.role === "tool" && !pausedStage) {
3220
+ const isExpectedExit = state.ctx?.__awaitingApproval || state.ctx?.__cancelled || state.ctx?.__finalizedDueToStructuredOutput || state.ctx?.__finalizedDueToToolLimit || state.ctx?.__needsSummarization;
3221
+ if (!isExpectedExit) {
3222
+ throw new Error(
3223
+ "Agent loop terminated with a pending tool response but no subsequent model invocation. This usually indicates a model provider error or exhausted iteration budget."
3224
+ );
3225
+ }
3226
+ }
3197
3227
  if (!pausedStage && typeof onStateChange === "function" && onStateChange(state)) {
3198
3228
  checkpointIfRequested("after_loop");
3199
3229
  }
@@ -3263,10 +3293,8 @@ function createAgent(opts) {
3263
3293
  agentRuntime: res.agent || runtime,
3264
3294
  status: "success"
3265
3295
  });
3266
- const finalMsg = res.messages[res.messages.length - 1];
3267
- let content = "";
3268
- if (typeof finalMsg?.content === "string") content = finalMsg.content;
3269
- else if (Array.isArray(finalMsg?.content)) content = finalMsg.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("");
3296
+ const finalAssistantMsg = getLastAssistantMessage(res.messages);
3297
+ const content = getMessageText(finalAssistantMsg);
3270
3298
  let parsed = void 0;
3271
3299
  const schema = opts.outputSchema;
3272
3300
  if (schema && res.ctx?.__structuredOutputParsed) {
@@ -3287,7 +3315,7 @@ function createAgent(opts) {
3287
3315
  } catch {
3288
3316
  }
3289
3317
  }
3290
- emit({ type: "finalAnswer", content: typeof finalMsg?.content === "string" ? finalMsg.content : content });
3318
+ emit({ type: "finalAnswer", content });
3291
3319
  if (streamEnabled && content) {
3292
3320
  onStream?.({ text: content, isFinal: true });
3293
3321
  emit({ type: "stream", text: content, isFinal: true });
@@ -3557,7 +3585,7 @@ function estimateContextRotScore(state) {
3557
3585
  const summaryCount = state.summaryRecords?.length || 0;
3558
3586
  const archivedCount = state.toolHistoryArchived?.length || 0;
3559
3587
  const messageCount = state.messages?.length || 0;
3560
- const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && message.content === "SUMMARIZED").length || 0;
3588
+ const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && typeof message.content === "string" && (message.content === "SUMMARIZED" || message.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))).length || 0;
3561
3589
  const numerator = summaryCount * 2 + archivedCount + summarizedMessages;
3562
3590
  const denominator = Math.max(messageCount, 1);
3563
3591
  return Number((numerator / denominator).toFixed(3));
@@ -3674,6 +3702,69 @@ function extractCanonicalFacts(messages) {
3674
3702
  }
3675
3703
  return [...facts.values()];
3676
3704
  }
3705
+ function flattenToolMessageContent(message) {
3706
+ if (typeof message.content === "string") {
3707
+ return message.content;
3708
+ }
3709
+ if (Array.isArray(message.content)) {
3710
+ return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? String(part ?? "")).join(" ");
3711
+ }
3712
+ return "";
3713
+ }
3714
+ function isSummarizedToolPlaceholder(content) {
3715
+ return typeof content === "string" && (content === "SUMMARIZED" || content.startsWith("SUMMARIZED_TOOL_RESPONSE"));
3716
+ }
3717
+ function summarizeToolMessagePreview(message) {
3718
+ const preview = flattenToolMessageContent(message).trim();
3719
+ if (!preview) {
3720
+ return "No summary available.";
3721
+ }
3722
+ return preview.length > 240 ? `${preview.slice(0, 240)}...` : preview;
3723
+ }
3724
+ function findToolHistoryEntry(state, toolCallId, toolName) {
3725
+ const entries = [
3726
+ ...state.toolHistory || [],
3727
+ ...state.toolHistoryArchived || []
3728
+ ];
3729
+ if (toolCallId) {
3730
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
3731
+ const entry = entries[index];
3732
+ if (entry?.tool_call_id === toolCallId) {
3733
+ return entry;
3734
+ }
3735
+ }
3736
+ }
3737
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
3738
+ const entry = entries[index];
3739
+ if (entry?.toolName === toolName) {
3740
+ return entry;
3741
+ }
3742
+ }
3743
+ return void 0;
3744
+ }
3745
+ function buildSummarizedToolMessage(message, state) {
3746
+ const toolName = message.name || "unknown_tool";
3747
+ const toolCallId = message.tool_call_id;
3748
+ const historyEntry = findToolHistoryEntry(state, toolCallId, toolName);
3749
+ const executionId = historyEntry?.executionId;
3750
+ const summary = historyEntry?.summary || summarizeToolMessagePreview(message);
3751
+ const references = [
3752
+ `toolName=${toolName}`,
3753
+ toolCallId ? `toolCallId=${toolCallId}` : null,
3754
+ executionId ? `executionId=${executionId}` : null
3755
+ ].filter(Boolean).join("; ");
3756
+ let retrievalHint = "Use get_tool_response if exact details are still needed.";
3757
+ if (executionId && toolCallId) {
3758
+ retrievalHint = `Use get_tool_response with executionId "${executionId}" or tool call id "${toolCallId}" if exact details are still needed.`;
3759
+ } else if (executionId || toolCallId) {
3760
+ retrievalHint = `Use get_tool_response with executionId "${executionId || toolCallId}" if exact details are still needed.`;
3761
+ }
3762
+ return [
3763
+ `SUMMARIZED_TOOL_RESPONSE [${references}]`,
3764
+ `Summary: ${summary}`,
3765
+ retrievalHint
3766
+ ].join("\n");
3767
+ }
3677
3768
  function mergeStableFacts(summary, additionalFacts) {
3678
3769
  if (additionalFacts.length === 0) return summary;
3679
3770
  const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
@@ -3732,7 +3823,7 @@ function createContextSummarizeNode(opts) {
3732
3823
  const onEvent = state.ctx?.__onEvent;
3733
3824
  const traceSession = state.ctx?.__traceSession;
3734
3825
  const compressableMessages = messages.filter(
3735
- (m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
3826
+ (m) => m.role === "tool" && !isSummarizedToolPlaceholder(m.content) && !isSyntheticSummaryToolMessage(m)
3736
3827
  );
3737
3828
  if (compressableMessages.length === 0) {
3738
3829
  return {};
@@ -3897,10 +3988,10 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
3897
3988
  if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
3898
3989
  return null;
3899
3990
  }
3900
- if (m.content === "SUMMARIZED") return m;
3991
+ if (isSummarizedToolPlaceholder(m.content)) return m;
3901
3992
  return {
3902
3993
  ...m,
3903
- content: "SUMMARIZED"
3994
+ content: buildSummarizedToolMessage(m, state)
3904
3995
  };
3905
3996
  }
3906
3997
  return m;
@@ -4210,8 +4301,8 @@ function createContextTools(stateRef, opts) {
4210
4301
  }
4211
4302
  const getTool = createTool({
4212
4303
  name: "get_tool_response",
4213
- 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.",
4214
- schema: z.object({ executionId: z.string().describe("Tool execution id") }),
4304
+ 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.",
4305
+ schema: z.object({ executionId: z.string().describe("Tool execution id or original tool_call_id") }),
4215
4306
  func: async ({ executionId }) => {
4216
4307
  const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
4217
4308
  let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));