@cognipeer/agent-sdk 0.1.2 → 0.2.0

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,19 @@ function createAgent(opts) {
1817
1872
  }
1818
1873
  }
1819
1874
  }
1875
+ let maxTok = 5e4;
1876
+ if (opts.summarization && typeof opts.summarization === "object" && opts.summarization.maxTokens) {
1877
+ maxTok = opts.summarization.maxTokens;
1878
+ }
1879
+ if (maxTok) {
1880
+ 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");
1881
+ const tokenCount = countApproxTokens(allText);
1882
+ if (tokenCount > maxTok) {
1883
+ const ctx = { ...state.ctx || {}, __needsSummarization: true };
1884
+ state = { ...state, ctx };
1885
+ break;
1886
+ }
1887
+ }
1820
1888
  state = { ...state, ...await agentCore(state) };
1821
1889
  if (checkpointIfRequested("after_agent")) break;
1822
1890
  const postGuardrails = getGuardrailConfig(state);
@@ -1890,6 +1958,35 @@ function createAgent(opts) {
1890
1958
  if (checkpointIfRequested("after_tools")) break;
1891
1959
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
1892
1960
  }
1961
+ if (opts.outputSchema && !state.ctx?.__finalizedDueToStructuredOutput) {
1962
+ const last = state.messages[state.messages.length - 1];
1963
+ const lastHasToolCalls = Array.isArray(last?.tool_calls) && last.tool_calls.length > 0;
1964
+ if (!lastHasToolCalls) {
1965
+ const forceMsg = {
1966
+ role: "system",
1967
+ content: [
1968
+ "A structured output schema is active.",
1969
+ "You MUST now call tool `response` with the final JSON object that matches the schema.",
1970
+ "Do not write the JSON in the assistant message.",
1971
+ "Call `response` exactly once, then stop."
1972
+ ].join("\n")
1973
+ };
1974
+ const alreadyForced = Boolean(state.ctx?.__structuredOutputForceFinalize);
1975
+ if (!alreadyForced) {
1976
+ const ctx = { ...state.ctx || {}, __structuredOutputForceFinalize: true };
1977
+ state = { ...state, messages: [...state.messages, forceMsg], ctx };
1978
+ try {
1979
+ state = { ...state, ...await agentCore(state) };
1980
+ const lastAfter = state.messages[state.messages.length - 1];
1981
+ const toolCallsAfter = Array.isArray(lastAfter?.tool_calls) ? lastAfter.tool_calls : [];
1982
+ if (toolCallsAfter.length > 0) {
1983
+ state = { ...state, ...await toolsNode(state) };
1984
+ }
1985
+ } catch {
1986
+ }
1987
+ }
1988
+ }
1989
+ }
1893
1990
  if (!pausedStage && typeof onStateChange === "function" && onStateChange(state)) {
1894
1991
  checkpointIfRequested("after_loop");
1895
1992
  }
@@ -2124,234 +2221,96 @@ function createAgentNode(opts) {
2124
2221
  };
2125
2222
  }
2126
2223
 
2127
- // src/utils/utilTokens.ts
2128
- function countApproxTokens(text) {
2129
- if (!text) return 0;
2130
- return Math.ceil(text.length / 4);
2131
- }
2132
-
2133
2224
  // src/nodes/contextSummarize.ts
2134
2225
  var systemMessage = (content) => ({ role: "system", content });
2135
2226
  var humanMessage = (content) => ({ role: "user", content });
2136
- var toolMessage = (name, content, tool_call_id) => ({ role: "tool", name, content, tool_call_id });
2227
+ function getSummarizationConfig(opts) {
2228
+ if (typeof opts.summarization === "object") {
2229
+ return {
2230
+ enabled: opts.summarization.enable !== false,
2231
+ maxTokens: opts.summarization.maxTokens
2232
+ };
2233
+ }
2234
+ return {
2235
+ enabled: opts.summarization !== false,
2236
+ maxTokens: 5e4
2237
+ };
2238
+ }
2137
2239
  function createContextSummarizeNode(opts) {
2138
2240
  return async (state) => {
2139
- const messages = state.messages || [];
2140
- const summaryTokenLimit = opts.limits?.summaryTokenLimit ?? 1e5;
2241
+ const config = getSummarizationConfig(opts);
2242
+ if (!config.enabled) return {};
2141
2243
  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();
2153
- }
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
- }
2244
+ const messages = state.messages || [];
2245
+ const conversationText = messages.map((m) => {
2246
+ let content = "";
2247
+ if (typeof m.content === "string") content = m.content;
2248
+ else if (Array.isArray(m.content)) {
2249
+ content = m.content.map((c) => c.text || JSON.stringify(c)).join(" ");
2234
2250
  }
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.
2251
+ let prefix = `${m.role.toUpperCase()}`;
2252
+ if (m.name) prefix += ` (${m.name})`;
2253
+ return `${prefix}: ${content}`;
2254
+ }).join("\n\n");
2255
+ const summaryPrompt = [
2256
+ systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
2257
+ humanMessage(
2258
+ `Please summarize the following conversation history.
2259
+ Focus on:
2260
+ - User goals and intent.
2261
+ - Key decisions made and actions taken.
2262
+ - Important tool outputs and data retrieved.
2263
+ - Current state of the task.
2258
2264
 
2259
- ${g.join("\n")}
2265
+ Conversation:
2266
+ ${conversationText}
2260
2267
 
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.";
2268
+ Summary:`
2269
+ )
2270
+ ];
2271
+ let summaryText = "Summary unavailable.";
2272
+ try {
2273
+ const response = await model.invoke(summaryPrompt);
2274
+ summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2275
+ } catch (err) {
2276
+ console.error("[ContextSummarize] Failed to generate summary:", err);
2277
+ return {};
2279
2278
  }
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;
2279
+ const newMessages = messages.map((m) => {
2280
+ if (m.role === "tool") {
2281
+ if (m.content === "SUMMARIZED") return m;
2282
+ return {
2283
+ ...m,
2284
+ content: "SUMMARIZED"
2285
+ };
2320
2286
  }
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 = {
2287
+ return m;
2288
+ });
2289
+ const summaryToolCallId = `call_summary_${Date.now()}`;
2290
+ const assistantSummaryCall = {
2326
2291
  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
- }
2292
+ // "assistant"
2293
+ content: "Context limit reached. Summarizing conversation history to reduce token usage.",
2294
+ tool_calls: [
2295
+ {
2296
+ id: summaryToolCallId,
2297
+ type: "function",
2298
+ function: {
2299
+ name: "summarize_context",
2300
+ arguments: "{}"
2301
+ }
2302
+ }
2303
+ ]
2333
2304
  };
2334
- const syntheticToolResp = {
2305
+ const toolSummaryResponse = {
2335
2306
  role: "tool",
2336
- content: `${summaryText}`,
2337
- tool_call_id: summarizeCallId,
2338
- name: "context_summarize"
2307
+ tool_call_id: summaryToolCallId,
2308
+ name: "summarize_context",
2309
+ // Matches the tool name in the call
2310
+ content: summaryText
2339
2311
  };
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
2312
  return {
2351
- messages: [...rewritten, syntheticAssistant, syntheticToolResp],
2352
- toolHistoryArchived: newArchived,
2353
- toolHistory: [],
2354
- ctx: { ...state.ctx || {}, __contextSummarized: true }
2313
+ messages: [...newMessages, assistantSummaryCall, toolSummaryResponse]
2355
2314
  };
2356
2315
  };
2357
2316
  }
@@ -2445,13 +2404,16 @@ function createContextTools(stateRef, opts) {
2445
2404
 
2446
2405
  // src/graph/decisions.ts
2447
2406
  function needsSummarization(state, opts, summarizationEnabled) {
2448
- const maxTok = opts.limits?.maxToken;
2449
- if (!maxTok || !summarizationEnabled) return false;
2407
+ const maxTok = opts.limits?.maxToken ?? opts.limits?.contextTokenLimit;
2408
+ if (!maxTok || !summarizationEnabled) {
2409
+ return false;
2410
+ }
2450
2411
  try {
2451
2412
  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
2413
  const tokenCount = countApproxTokens(allText);
2453
- return tokenCount > maxTok;
2454
- } catch {
2414
+ const needsSum = tokenCount > maxTok;
2415
+ return needsSum;
2416
+ } catch (e) {
2455
2417
  return false;
2456
2418
  }
2457
2419
  }
@@ -2476,7 +2438,12 @@ function createSmartAgent(opts) {
2476
2438
  const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
2477
2439
  const mergedTools = [...opts.tools ?? [], ...contextTools];
2478
2440
  const base = createAgent({ ...opts, tools: mergedTools });
2479
- const summarizationEnabled = opts.summarization !== false;
2441
+ let summarizationEnabled = false;
2442
+ if (typeof opts.summarization === "object") {
2443
+ summarizationEnabled = opts.summarization.enable !== false;
2444
+ } else {
2445
+ summarizationEnabled = opts.summarization !== false;
2446
+ }
2480
2447
  const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
2481
2448
  const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
2482
2449
  const decideAfter = toolsDecisionFactory(opts, summarizationEnabled);
@@ -2513,6 +2480,14 @@ function createSmartAgent(opts) {
2513
2480
  const res = await base.invoke(state, config);
2514
2481
  lastResult = res;
2515
2482
  state = res.state || { ...state, messages: res.messages };
2483
+ if (state.ctx?.__needsSummarization && summarizer) {
2484
+ const ctx = { ...state.ctx || {} };
2485
+ delete ctx.__needsSummarization;
2486
+ state = { ...state, ctx };
2487
+ const delta = await summarizer(state);
2488
+ state = { ...state, ...delta };
2489
+ continue;
2490
+ }
2516
2491
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
2517
2492
  if (summarizationEnabled) {
2518
2493
  const after = decideAfter(state);