@cognipeer/agent-sdk 0.1.2 → 0.2.1

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.d.cts CHANGED
@@ -131,9 +131,6 @@ type GuardrailOutcome = {
131
131
  };
132
132
  type AgentLimits = {
133
133
  maxToolCalls?: number;
134
- contextTokenLimit?: number;
135
- summaryTokenLimit?: number;
136
- maxToken?: number;
137
134
  maxParallelTools?: number;
138
135
  };
139
136
  type SmartAgentLimits = AgentLimits;
@@ -183,7 +180,10 @@ type SmartAgentOptions = {
183
180
  guardrails?: ConversationGuardrail[];
184
181
  handoffs?: HandoffDescriptor[];
185
182
  limits?: AgentLimits;
186
- summarization?: boolean;
183
+ summarization?: boolean | {
184
+ enable: boolean;
185
+ maxTokens: number;
186
+ };
187
187
  systemPrompt?: string;
188
188
  useTodoList?: boolean;
189
189
  usageConverter?: (finalMessage: AIMessage, fullState: SmartState, model: any) => any;
@@ -616,6 +616,18 @@ declare function createResolverNode(): (state: SmartState) => Promise<SmartState
616
616
 
617
617
  declare function createToolLimitFinalizeNode(_opts: SmartAgentOptions): (state: SmartState) => Promise<Partial<SmartState>>;
618
618
 
619
+ /**
620
+ * Creates the summarization node.
621
+ *
622
+ * Logic:
623
+ * 1. Check if summarization is enabled.
624
+ * 2. Convert current conversation history into a text suitable for summarization.
625
+ * 3. Invoke the model to generate a summary of the conversation.
626
+ * 4. Rewrite the message history:
627
+ * - All tool messages (role='tool') have their content replaced with "SUMMARIZED".
628
+ * - A new assistant message calling 'summarize_context' is appended.
629
+ * - A new tool message with the summary content is appended.
630
+ */
619
631
  declare function createContextSummarizeNode(opts: SmartAgentOptions): (state: SmartState) => Promise<Partial<SmartState>>;
620
632
 
621
633
  type TokenLimits = {
package/dist/index.d.ts CHANGED
@@ -131,9 +131,6 @@ type GuardrailOutcome = {
131
131
  };
132
132
  type AgentLimits = {
133
133
  maxToolCalls?: number;
134
- contextTokenLimit?: number;
135
- summaryTokenLimit?: number;
136
- maxToken?: number;
137
134
  maxParallelTools?: number;
138
135
  };
139
136
  type SmartAgentLimits = AgentLimits;
@@ -183,7 +180,10 @@ type SmartAgentOptions = {
183
180
  guardrails?: ConversationGuardrail[];
184
181
  handoffs?: HandoffDescriptor[];
185
182
  limits?: AgentLimits;
186
- summarization?: boolean;
183
+ summarization?: boolean | {
184
+ enable: boolean;
185
+ maxTokens: number;
186
+ };
187
187
  systemPrompt?: string;
188
188
  useTodoList?: boolean;
189
189
  usageConverter?: (finalMessage: AIMessage, fullState: SmartState, model: any) => any;
@@ -616,6 +616,18 @@ declare function createResolverNode(): (state: SmartState) => Promise<SmartState
616
616
 
617
617
  declare function createToolLimitFinalizeNode(_opts: SmartAgentOptions): (state: SmartState) => Promise<Partial<SmartState>>;
618
618
 
619
+ /**
620
+ * Creates the summarization node.
621
+ *
622
+ * Logic:
623
+ * 1. Check if summarization is enabled.
624
+ * 2. Convert current conversation history into a text suitable for summarization.
625
+ * 3. Invoke the model to generate a summary of the conversation.
626
+ * 4. Rewrite the message history:
627
+ * - All tool messages (role='tool') have their content replaced with "SUMMARIZED".
628
+ * - A new assistant message calling 'summarize_context' is appended.
629
+ * - A new tool message with the summary content is appended.
630
+ */
619
631
  declare function createContextSummarizeNode(opts: SmartAgentOptions): (state: SmartState) => Promise<Partial<SmartState>>;
620
632
 
621
633
  type TokenLimits = {
package/dist/index.mjs CHANGED
@@ -1096,9 +1096,58 @@ function createAgentCoreNode(opts) {
1096
1096
  const shouldLogPrompt = !!traceSession && traceSession.resolvedConfig.logData;
1097
1097
  const promptPayload = shouldLogPrompt ? sanitizeTracePayload(state.messages) : void 0;
1098
1098
  const promptBytes = promptPayload !== void 0 ? estimatePayloadBytes(promptPayload) : void 0;
1099
+ const extractToolUses = (m) => {
1100
+ const tcs = m?.tool_calls || m?.additional_kwargs?.tool_calls;
1101
+ if (!Array.isArray(tcs)) return [];
1102
+ return tcs.map((tc) => ({ id: tc?.id, name: tc?.function?.name || tc?.name })).filter((x) => typeof x.id === "string" && x.id.length > 0);
1103
+ };
1104
+ const normalizeBedrockToolPairing = (input) => {
1105
+ const msgs = Array.isArray(input) ? [...input] : [];
1106
+ for (let i = 0; i < msgs.length; i++) {
1107
+ const m = msgs[i];
1108
+ if (m?.role !== "assistant") continue;
1109
+ const toolUses = extractToolUses(m);
1110
+ if (toolUses.length === 0) continue;
1111
+ const next = msgs[i + 1];
1112
+ const firstId = toolUses[0]?.id;
1113
+ const nextIsToolForFirst = !!(next && next.role === "tool" && next.tool_call_id === firstId);
1114
+ if (nextIsToolForFirst) continue;
1115
+ const placeholders = toolUses.map((tu) => ({
1116
+ role: "tool",
1117
+ name: tu.name || "unknown_tool",
1118
+ tool_call_id: tu.id,
1119
+ content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for Bedrock tool_result adjacency."
1120
+ }));
1121
+ msgs.splice(i + 1, 0, ...placeholders);
1122
+ i += placeholders.length;
1123
+ }
1124
+ return msgs;
1125
+ };
1126
+ const debugToolPairing = (messagesToSend) => {
1127
+ try {
1128
+ const msgs = Array.isArray(messagesToSend) ? messagesToSend : [];
1129
+ const toolUseIds = [];
1130
+ const missingResults = [];
1131
+ for (let i = 0; i < msgs.length; i++) {
1132
+ const m = msgs[i];
1133
+ if (m?.role === "assistant") {
1134
+ for (const tu of extractToolUses(m)) {
1135
+ toolUseIds.push(tu.id);
1136
+ const next = msgs[i + 1];
1137
+ if (!(next && next.role === "tool" && (next.tool_call_id === tu.id || next.tool_call_id === m?.tool_call_id))) {
1138
+ missingResults.push({ id: tu.id, atIndex: i, tool: tu.name });
1139
+ }
1140
+ }
1141
+ }
1142
+ }
1143
+ } catch (e) {
1144
+ }
1145
+ };
1099
1146
  let response;
1100
1147
  try {
1101
- response = await modelWithTools.invoke([...state.messages]);
1148
+ const normalizedMessages = normalizeBedrockToolPairing([...state.messages]);
1149
+ debugToolPairing(normalizedMessages);
1150
+ response = await modelWithTools.invoke(normalizedMessages);
1102
1151
  } catch (err) {
1103
1152
  const durationMs2 = Date.now() - start;
1104
1153
  recordTraceEvent(traceSession, {
@@ -1685,6 +1734,12 @@ function resolveToolApprovalState(state, resolution) {
1685
1734
  };
1686
1735
  }
1687
1736
 
1737
+ // src/utils/utilTokens.ts
1738
+ function countApproxTokens(text) {
1739
+ if (!text) return 0;
1740
+ return Math.ceil(text.length / 4);
1741
+ }
1742
+
1688
1743
  // src/agent.ts
1689
1744
  function createAgent(opts) {
1690
1745
  const resolver = createResolverNode();
@@ -1749,8 +1804,8 @@ function createAgent(opts) {
1749
1804
  delete nextCtx.__resumeStage;
1750
1805
  state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
1751
1806
  }
1752
- const maxToolCalls = opts.limits?.maxToolCalls ?? 10;
1753
- const iterationLimit = Math.max(maxToolCalls * 3 + 10, 40);
1807
+ const maxToolCalls = opts.limits?.maxToolCalls === void 0 ? 50 : opts.limits?.maxToolCalls;
1808
+ const iterationLimit = maxToolCalls === Infinity ? 100 : Math.max(maxToolCalls * 3 + 10, 40);
1754
1809
  let iterations = 0;
1755
1810
  const onStateChange = config?.onStateChange;
1756
1811
  const checkpointReason = config?.checkpointReason;
@@ -1817,6 +1872,22 @@ function createAgent(opts) {
1817
1872
  }
1818
1873
  }
1819
1874
  }
1875
+ let maxTok;
1876
+ if (opts.summarization && typeof opts.summarization === "object" && opts.summarization.maxTokens) {
1877
+ maxTok = opts.summarization.maxTokens;
1878
+ }
1879
+ if (maxTok === void 0) {
1880
+ maxTok = 5e4;
1881
+ }
1882
+ if (maxTok) {
1883
+ 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");
1884
+ const tokenCount = countApproxTokens(allText);
1885
+ if (tokenCount > maxTok) {
1886
+ const ctx = { ...state.ctx || {}, __needsSummarization: true };
1887
+ state = { ...state, ctx };
1888
+ break;
1889
+ }
1890
+ }
1820
1891
  state = { ...state, ...await agentCore(state) };
1821
1892
  if (checkpointIfRequested("after_agent")) break;
1822
1893
  const postGuardrails = getGuardrailConfig(state);
@@ -1890,6 +1961,35 @@ function createAgent(opts) {
1890
1961
  if (checkpointIfRequested("after_tools")) break;
1891
1962
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
1892
1963
  }
1964
+ if (opts.outputSchema && !state.ctx?.__finalizedDueToStructuredOutput) {
1965
+ const last = state.messages[state.messages.length - 1];
1966
+ const lastHasToolCalls = Array.isArray(last?.tool_calls) && last.tool_calls.length > 0;
1967
+ if (!lastHasToolCalls) {
1968
+ const forceMsg = {
1969
+ role: "system",
1970
+ content: [
1971
+ "A structured output schema is active.",
1972
+ "You MUST now call tool `response` with the final JSON object that matches the schema.",
1973
+ "Do not write the JSON in the assistant message.",
1974
+ "Call `response` exactly once, then stop."
1975
+ ].join("\n")
1976
+ };
1977
+ const alreadyForced = Boolean(state.ctx?.__structuredOutputForceFinalize);
1978
+ if (!alreadyForced) {
1979
+ const ctx = { ...state.ctx || {}, __structuredOutputForceFinalize: true };
1980
+ state = { ...state, messages: [...state.messages, forceMsg], ctx };
1981
+ try {
1982
+ state = { ...state, ...await agentCore(state) };
1983
+ const lastAfter = state.messages[state.messages.length - 1];
1984
+ const toolCallsAfter = Array.isArray(lastAfter?.tool_calls) ? lastAfter.tool_calls : [];
1985
+ if (toolCallsAfter.length > 0) {
1986
+ state = { ...state, ...await toolsNode(state) };
1987
+ }
1988
+ } catch {
1989
+ }
1990
+ }
1991
+ }
1992
+ }
1893
1993
  if (!pausedStage && typeof onStateChange === "function" && onStateChange(state)) {
1894
1994
  checkpointIfRequested("after_loop");
1895
1995
  }
@@ -2124,234 +2224,100 @@ function createAgentNode(opts) {
2124
2224
  };
2125
2225
  }
2126
2226
 
2127
- // src/utils/utilTokens.ts
2128
- function countApproxTokens(text) {
2129
- if (!text) return 0;
2130
- return Math.ceil(text.length / 4);
2131
- }
2132
-
2133
2227
  // src/nodes/contextSummarize.ts
2134
2228
  var systemMessage = (content) => ({ role: "system", content });
2135
2229
  var humanMessage = (content) => ({ role: "user", content });
2136
- var toolMessage = (name, content, tool_call_id) => ({ role: "tool", name, content, tool_call_id });
2230
+ function getSummarizationConfig(opts) {
2231
+ if (typeof opts.summarization === "object") {
2232
+ return {
2233
+ enabled: opts.summarization.enable !== false,
2234
+ maxTokens: opts.summarization.maxTokens
2235
+ };
2236
+ }
2237
+ return {
2238
+ enabled: opts.summarization !== false,
2239
+ maxTokens: 5e4
2240
+ };
2241
+ }
2137
2242
  function createContextSummarizeNode(opts) {
2138
2243
  return async (state) => {
2139
- const messages = state.messages || [];
2140
- const summaryTokenLimit = opts.limits?.summaryTokenLimit ?? 1e5;
2244
+ const config = getSummarizationConfig(opts);
2245
+ if (!config.enabled) return {};
2141
2246
  const model = opts.model;
2142
- const archived = state.toolHistoryArchived ? [...state.toolHistoryArchived] : [];
2143
- const live = state.toolHistory ? [...state.toolHistory] : [];
2144
- const byCallId = /* @__PURE__ */ new Map();
2145
- for (const item of archived) {
2146
- if (item.tool_call_id) byCallId.set(item.tool_call_id, item);
2147
- }
2148
- for (const item of live) {
2149
- if (item.tool_call_id) byCallId.set(item.tool_call_id, item);
2150
- }
2151
- for (const item of live) {
2152
- if (!item.executionId) item.executionId = nanoid();
2247
+ const messages = state.messages || [];
2248
+ const compressableMessages = messages.filter((m) => m.role === "tool" && m.content !== "SUMMARIZED");
2249
+ if (compressableMessages.length === 0) {
2250
+ return {};
2153
2251
  }
2154
- const getText = (m) => {
2155
- if (!m) return "";
2156
- const c = m.content;
2157
- if (typeof c === "string") return c;
2158
- if (Array.isArray(c)) return c.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("");
2159
- return String(c ?? "");
2160
- };
2161
- const sysText = "You are a concise summarization assistant for a tool-using agent. Summarize prior tool executions into a compact brief that preserves key facts, decisions, and outputs. Reference executionId values where helpful. Avoid repeating raw data; prefer synthesis.";
2162
- const sys = systemMessage(sysText);
2163
- let summaryText = "";
2164
- try {
2165
- const limit = Math.max(1e3, Number(summaryTokenLimit) || 1e5);
2166
- const safetyBuffer = Math.floor(Math.min(4e3, limit * 0.05));
2167
- const perCallBudget = Math.max(500, limit - safetyBuffer);
2168
- const safeToolPair = opts?.safeToolPair !== false;
2169
- const chunks = [];
2170
- let current = [];
2171
- let currentTokens = countApproxTokens(sysText);
2172
- let pendingToolCalls = /* @__PURE__ */ new Set();
2173
- const extractToolCallIds = (msg) => {
2174
- const tc = msg?.additional_kwargs?.tool_calls;
2175
- if (!Array.isArray(tc) || tc.length === 0) return [];
2176
- return tc.map((c) => c?.id).filter((id) => typeof id === "string");
2177
- };
2178
- for (let i = 0; i < messages.length; i++) {
2179
- const m = messages[i];
2180
- const text = getText(m);
2181
- const tks = countApproxTokens(text);
2182
- const isAIWithToolCalls = safeToolPair && m?.role === "assistant" && extractToolCallIds(m).length > 0;
2183
- const isToolMsg = safeToolPair && m?.role === "tool";
2184
- const toolCallId = isToolMsg ? m.tool_call_id : void 0;
2185
- const wouldExceed = currentTokens + tks > perCallBudget && current.length > 0;
2186
- if (wouldExceed) {
2187
- let forceAddToCurrent = false;
2188
- if (safeToolPair) {
2189
- if (isToolMsg && toolCallId && pendingToolCalls.has(toolCallId)) {
2190
- forceAddToCurrent = true;
2191
- } else if (isAIWithToolCalls) {
2192
- } else if (pendingToolCalls.size > 0) {
2193
- }
2194
- }
2195
- if (!forceAddToCurrent) {
2196
- if (current.length > 0) {
2197
- chunks.push(current);
2198
- }
2199
- current = [];
2200
- currentTokens = countApproxTokens(sysText);
2201
- pendingToolCalls = /* @__PURE__ */ new Set();
2202
- }
2203
- }
2204
- current.push(m);
2205
- currentTokens += tks;
2206
- if (isAIWithToolCalls) {
2207
- for (const id of extractToolCallIds(m)) pendingToolCalls.add(id);
2208
- }
2209
- if (isToolMsg && toolCallId && pendingToolCalls.has(toolCallId)) {
2210
- pendingToolCalls.delete(toolCallId);
2211
- }
2212
- if (currentTokens >= perCallBudget && pendingToolCalls.size === 0) {
2213
- chunks.push(current);
2214
- current = [];
2215
- currentTokens = countApproxTokens(sysText);
2216
- pendingToolCalls = /* @__PURE__ */ new Set();
2217
- }
2218
- }
2219
- if (current.length > 0) {
2220
- chunks.push(current);
2221
- }
2222
- const partials = [];
2223
- for (const chunk of chunks) {
2224
- let resp = null;
2225
- try {
2226
- resp = await (model?.invoke ? model.invoke([sys, ...chunk]) : null);
2227
- const c = resp?.content;
2228
- const text = typeof c === "string" ? c : Array.isArray(c) ? c.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("") : "";
2229
- partials.push((text || "").trim());
2230
- } catch (e) {
2231
- console.error("Summarization chunk failed:", e);
2232
- partials.push("[summary unavailable for one chunk]");
2233
- }
2252
+ const conversationText = messages.map((m) => {
2253
+ let content = "";
2254
+ if (typeof m.content === "string") content = m.content;
2255
+ else if (Array.isArray(m.content)) {
2256
+ content = m.content.map((c) => c.text || JSON.stringify(c)).join(" ");
2234
2257
  }
2235
- const mergeSysText = "You merge partial summaries into a single cohesive, non-redundant summary that preserves key facts and decisions. Be concise.";
2236
- const mergeSys = systemMessage(mergeSysText);
2237
- const mergeIteratively = async (texts) => {
2238
- if (texts.length === 0) return "";
2239
- if (texts.length === 1) return texts[0];
2240
- const groups = [];
2241
- let group = [];
2242
- let toks = countApproxTokens(mergeSysText);
2243
- for (const t of texts) {
2244
- const tt = `- ${t}`;
2245
- const tk = countApproxTokens(tt);
2246
- if (toks + tk > perCallBudget && group.length > 0) {
2247
- groups.push(group);
2248
- group = [];
2249
- toks = countApproxTokens(mergeSysText);
2250
- }
2251
- group.push(tt);
2252
- toks += tk;
2253
- }
2254
- if (group.length > 0) groups.push(group);
2255
- const next = [];
2256
- for (const g of groups) {
2257
- const mergePrompt = humanMessage(`Merge the following partial summaries into a single concise brief. Avoid duplication, keep key facts and decisions.
2258
+ let prefix = `${m.role.toUpperCase()}`;
2259
+ if (m.name) prefix += ` (${m.name})`;
2260
+ return `${prefix}: ${content}`;
2261
+ }).join("\n\n");
2262
+ const summaryPrompt = [
2263
+ systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
2264
+ humanMessage(
2265
+ `Please summarize the following conversation history.
2266
+ Focus on:
2267
+ - User goals and intent.
2268
+ - Key decisions made and actions taken.
2269
+ - Important tool outputs and data retrieved.
2270
+ - Current state of the task.
2258
2271
 
2259
- ${g.join("\n")}
2272
+ Conversation:
2273
+ ${conversationText}
2260
2274
 
2261
- Merged summary:`);
2262
- try {
2263
- const r = await (model?.invoke ? model.invoke([mergeSys, mergePrompt]) : null);
2264
- const cc = r?.content;
2265
- const txt = typeof cc === "string" ? cc : Array.isArray(cc) ? cc.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("") : "";
2266
- next.push((txt || "").trim());
2267
- } catch (e) {
2268
- console.error("Merge summarization failed:", e);
2269
- next.push(g.join(" "));
2270
- }
2271
- }
2272
- return mergeIteratively(next);
2273
- };
2274
- summaryText = await mergeIteratively(partials);
2275
- if (!summaryText) summaryText = "Context summarized.";
2276
- } catch (e) {
2277
- console.error("Error during context summarization:", e);
2278
- summaryText = "Context summarized.";
2275
+ Summary:`
2276
+ )
2277
+ ];
2278
+ let summaryText = "Summary unavailable.";
2279
+ try {
2280
+ const response = await model.invoke(summaryPrompt);
2281
+ summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2282
+ } catch (err) {
2283
+ console.error("[ContextSummarize] Failed to generate summary:", err);
2284
+ return {};
2279
2285
  }
2280
- const rewritten = [];
2281
- const alreadySummarized = (content) => {
2282
- const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("") : String(content ?? "");
2283
- return /\bSUMMARIZED\b/.test(text);
2284
- };
2285
- for (const m of messages) {
2286
- if (m.getType() === "tool") {
2287
- const toolMessageObj = m;
2288
- if (alreadySummarized(toolMessageObj.content) || toolMessageObj.name === "context_summarize" || toolMessageObj.name === "manage_todo_list") {
2289
- rewritten.push(m);
2290
- continue;
2291
- }
2292
- const callId = toolMessageObj.tool_call_id;
2293
- const safeId = callId || `summarized_${nanoid(6)}`;
2294
- let hist = callId ? byCallId.get(callId) : void 0;
2295
- if (!hist) {
2296
- hist = {
2297
- executionId: nanoid(),
2298
- toolName: toolMessageObj.name || "unknown_tool",
2299
- args: null,
2300
- output: typeof toolMessageObj.content === "string" ? toolMessageObj.content : Array.isArray(toolMessageObj.content) ? toolMessageObj.content.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("") : String(toolMessageObj.content ?? ""),
2301
- rawOutput: void 0,
2302
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2303
- summarized: true,
2304
- messageId: void 0,
2305
- tool_call_id: callId,
2306
- fromCache: false
2307
- };
2308
- if (callId) byCallId.set(callId, hist);
2309
- archived.push({ ...hist });
2310
- }
2311
- if (!hist.executionId) hist.executionId = nanoid();
2312
- hist.summarized = true;
2313
- rewritten.push({
2314
- role: "tool",
2315
- content: `SUMMARIZED executionId:'${hist.executionId}'`,
2316
- tool_call_id: safeId,
2317
- name: toolMessage.name
2318
- });
2319
- continue;
2286
+ const newMessages = messages.map((m) => {
2287
+ if (m.role === "tool") {
2288
+ if (m.content === "SUMMARIZED") return m;
2289
+ return {
2290
+ ...m,
2291
+ content: "SUMMARIZED"
2292
+ };
2320
2293
  }
2321
- rewritten.push(m);
2322
- }
2323
- const summarizeCallId = `summarize_${nanoid(6)}`;
2324
- const summarizeArgs = { reason: "Exceeded token limit: summarize prior tool outputs for context." };
2325
- const syntheticAssistant = {
2294
+ return m;
2295
+ });
2296
+ const summaryToolCallId = `call_summary_${Date.now()}`;
2297
+ const assistantSummaryCall = {
2326
2298
  role: "assistant",
2327
- content: "",
2328
- additional_kwargs: {
2329
- tool_calls: [
2330
- { id: summarizeCallId, type: "function", function: { name: "context_summarize", arguments: JSON.stringify(summarizeArgs) } }
2331
- ]
2332
- }
2299
+ // "assistant"
2300
+ content: "Context limit reached. Summarizing conversation history to reduce token usage.",
2301
+ tool_calls: [
2302
+ {
2303
+ id: summaryToolCallId,
2304
+ type: "function",
2305
+ function: {
2306
+ name: "summarize_context",
2307
+ arguments: "{}"
2308
+ }
2309
+ }
2310
+ ]
2333
2311
  };
2334
- const syntheticToolResp = {
2312
+ const toolSummaryResponse = {
2335
2313
  role: "tool",
2336
- content: `${summaryText}`,
2337
- tool_call_id: summarizeCallId,
2338
- name: "context_summarize"
2314
+ tool_call_id: summaryToolCallId,
2315
+ name: "summarize_context",
2316
+ // Matches the tool name in the call
2317
+ content: summaryText
2339
2318
  };
2340
- for (const item of live) item.summarized = true;
2341
- const merged = [...archived, ...live];
2342
- const dedupMap = /* @__PURE__ */ new Map();
2343
- for (const it of merged) {
2344
- const key = it.tool_call_id || `${it.toolName}:${it.timestamp}`;
2345
- dedupMap.set(key, { ...it });
2346
- }
2347
- const newArchived = Array.from(dedupMap.values());
2348
- const onEvent = state.ctx?.__onEvent;
2349
- onEvent?.({ type: "summarization", summary: summaryText, archivedCount: newArchived.length });
2350
2319
  return {
2351
- messages: [...rewritten, syntheticAssistant, syntheticToolResp],
2352
- toolHistoryArchived: newArchived,
2353
- toolHistory: [],
2354
- ctx: { ...state.ctx || {}, __contextSummarized: true }
2320
+ messages: [...newMessages, assistantSummaryCall, toolSummaryResponse]
2355
2321
  };
2356
2322
  };
2357
2323
  }
@@ -2445,13 +2411,22 @@ function createContextTools(stateRef, opts) {
2445
2411
 
2446
2412
  // src/graph/decisions.ts
2447
2413
  function needsSummarization(state, opts, summarizationEnabled) {
2448
- const maxTok = opts.limits?.maxToken;
2449
- if (!maxTok || !summarizationEnabled) return false;
2414
+ if (!summarizationEnabled) {
2415
+ return false;
2416
+ }
2417
+ let maxTok;
2418
+ if (typeof opts.summarization === "object" && typeof opts.summarization.maxTokens === "number") {
2419
+ maxTok = opts.summarization.maxTokens;
2420
+ }
2421
+ if (maxTok === void 0) {
2422
+ maxTok = 5e4;
2423
+ }
2450
2424
  try {
2451
2425
  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");
2452
2426
  const tokenCount = countApproxTokens(allText);
2453
- return tokenCount > maxTok;
2454
- } catch {
2427
+ const needsSum = tokenCount > maxTok;
2428
+ return needsSum;
2429
+ } catch (e) {
2455
2430
  return false;
2456
2431
  }
2457
2432
  }
@@ -2476,7 +2451,12 @@ function createSmartAgent(opts) {
2476
2451
  const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
2477
2452
  const mergedTools = [...opts.tools ?? [], ...contextTools];
2478
2453
  const base = createAgent({ ...opts, tools: mergedTools });
2479
- const summarizationEnabled = opts.summarization !== false;
2454
+ let summarizationEnabled = false;
2455
+ if (typeof opts.summarization === "object") {
2456
+ summarizationEnabled = opts.summarization.enable !== false;
2457
+ } else {
2458
+ summarizationEnabled = opts.summarization !== false;
2459
+ }
2480
2460
  const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
2481
2461
  const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
2482
2462
  const decideAfter = toolsDecisionFactory(opts, summarizationEnabled);
@@ -2507,19 +2487,33 @@ function createSmartAgent(opts) {
2507
2487
  const next = summarizationEnabled ? decideBefore(state) : "agent";
2508
2488
  if (next === "contextSummarize" && summarizer) {
2509
2489
  const delta = await summarizer(state);
2510
- state = { ...state, ...delta };
2511
- continue;
2490
+ if (!delta || Object.keys(delta).length === 0) ; else {
2491
+ state = { ...state, ...delta };
2492
+ continue;
2493
+ }
2512
2494
  }
2513
2495
  const res = await base.invoke(state, config);
2514
2496
  lastResult = res;
2515
2497
  state = res.state || { ...state, messages: res.messages };
2498
+ if (state.ctx?.__needsSummarization && summarizer) {
2499
+ const ctx = { ...state.ctx || {} };
2500
+ delete ctx.__needsSummarization;
2501
+ state = { ...state, ctx };
2502
+ const delta = await summarizer(state);
2503
+ if (delta && Object.keys(delta).length > 0) {
2504
+ state = { ...state, ...delta };
2505
+ }
2506
+ continue;
2507
+ }
2516
2508
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
2517
2509
  if (summarizationEnabled) {
2518
2510
  const after = decideAfter(state);
2519
2511
  if (after === "contextSummarize" && summarizer) {
2520
2512
  const delta = await summarizer(state);
2521
- state = { ...state, ...delta };
2522
- continue;
2513
+ if (delta && Object.keys(delta).length > 0) {
2514
+ state = { ...state, ...delta };
2515
+ continue;
2516
+ }
2523
2517
  }
2524
2518
  }
2525
2519
  break;