@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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cognipeer/agent-sdk
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@cognipeer/agent-sdk?color=success)](https://npmjs.com/package/@cognipeer/agent-sdk) [Docs Website](https://cognipeer.github.io/agent-sdk/)
3
+ [![npm](https://img.shields.io/npm/v/@cognipeer/agent-sdk?color=success)](https://npmjs.com/package/@cognipeer/agent-sdk) [Docs Website](https://cognipeer.github.io/agent-sdk/)
4
4
 
5
5
  Lightweight, message-first agent runtime that keeps tool calls transparent, automatically summarizes long histories, and ships with planning, multi-agent handoffs, and structured tracing.
6
6
 
@@ -158,12 +158,27 @@ Examples live under `examples/` with per-folder READMEs. Build the package first
158
158
  | `mcp-tavily/` | MCP remote tool discovery. |
159
159
  | `vision/` | Text + image input using LangChain’s OpenAI bindings. |
160
160
 
161
- Run directly with `tsx`, for example:
161
+ To run examples:
162
162
 
163
- ```sh
164
- # from repo root
165
- npm run build
166
- OPENAI_API_KEY=... npx tsx examples/tools/tools.ts
163
+ ```bash
164
+ # Install root dependencies
165
+ npm install
166
+
167
+ # Install example dependencies
168
+ cd examples
169
+ npm install
170
+
171
+ # Run an example from the examples directory
172
+ npm run example:basic
173
+ npm run example:tools
174
+ npm run example:multi-agent
175
+ ```
176
+
177
+ Or run directly with tsx:
178
+
179
+ ```bash
180
+ # From examples directory
181
+ OPENAI_API_KEY=... npx tsx basic/basic.ts
167
182
  ```
168
183
 
169
184
  ## Architecture snapshot
package/dist/index.cjs CHANGED
@@ -1105,9 +1105,58 @@ function createAgentCoreNode(opts) {
1105
1105
  const shouldLogPrompt = !!traceSession && traceSession.resolvedConfig.logData;
1106
1106
  const promptPayload = shouldLogPrompt ? sanitizeTracePayload(state.messages) : void 0;
1107
1107
  const promptBytes = promptPayload !== void 0 ? estimatePayloadBytes(promptPayload) : void 0;
1108
+ const extractToolUses = (m) => {
1109
+ const tcs = m?.tool_calls || m?.additional_kwargs?.tool_calls;
1110
+ if (!Array.isArray(tcs)) return [];
1111
+ return tcs.map((tc) => ({ id: tc?.id, name: tc?.function?.name || tc?.name })).filter((x) => typeof x.id === "string" && x.id.length > 0);
1112
+ };
1113
+ const normalizeBedrockToolPairing = (input) => {
1114
+ const msgs = Array.isArray(input) ? [...input] : [];
1115
+ for (let i = 0; i < msgs.length; i++) {
1116
+ const m = msgs[i];
1117
+ if (m?.role !== "assistant") continue;
1118
+ const toolUses = extractToolUses(m);
1119
+ if (toolUses.length === 0) continue;
1120
+ const next = msgs[i + 1];
1121
+ const firstId = toolUses[0]?.id;
1122
+ const nextIsToolForFirst = !!(next && next.role === "tool" && next.tool_call_id === firstId);
1123
+ if (nextIsToolForFirst) continue;
1124
+ const placeholders = toolUses.map((tu) => ({
1125
+ role: "tool",
1126
+ name: tu.name || "unknown_tool",
1127
+ tool_call_id: tu.id,
1128
+ content: "SUMMARIZED/DEFERRED: tool result missing in transcript; inserted placeholder for Bedrock tool_result adjacency."
1129
+ }));
1130
+ msgs.splice(i + 1, 0, ...placeholders);
1131
+ i += placeholders.length;
1132
+ }
1133
+ return msgs;
1134
+ };
1135
+ const debugToolPairing = (messagesToSend) => {
1136
+ try {
1137
+ const msgs = Array.isArray(messagesToSend) ? messagesToSend : [];
1138
+ const toolUseIds = [];
1139
+ const missingResults = [];
1140
+ for (let i = 0; i < msgs.length; i++) {
1141
+ const m = msgs[i];
1142
+ if (m?.role === "assistant") {
1143
+ for (const tu of extractToolUses(m)) {
1144
+ toolUseIds.push(tu.id);
1145
+ const next = msgs[i + 1];
1146
+ if (!(next && next.role === "tool" && (next.tool_call_id === tu.id || next.tool_call_id === m?.tool_call_id))) {
1147
+ missingResults.push({ id: tu.id, atIndex: i, tool: tu.name });
1148
+ }
1149
+ }
1150
+ }
1151
+ }
1152
+ } catch (e) {
1153
+ }
1154
+ };
1108
1155
  let response;
1109
1156
  try {
1110
- response = await modelWithTools.invoke([...state.messages]);
1157
+ const normalizedMessages = normalizeBedrockToolPairing([...state.messages]);
1158
+ debugToolPairing(normalizedMessages);
1159
+ response = await modelWithTools.invoke(normalizedMessages);
1111
1160
  } catch (err) {
1112
1161
  const durationMs2 = Date.now() - start;
1113
1162
  recordTraceEvent(traceSession, {
@@ -1694,6 +1743,12 @@ function resolveToolApprovalState(state, resolution) {
1694
1743
  };
1695
1744
  }
1696
1745
 
1746
+ // src/utils/utilTokens.ts
1747
+ function countApproxTokens(text) {
1748
+ if (!text) return 0;
1749
+ return Math.ceil(text.length / 4);
1750
+ }
1751
+
1697
1752
  // src/agent.ts
1698
1753
  function createAgent(opts) {
1699
1754
  const resolver = createResolverNode();
@@ -1758,8 +1813,8 @@ function createAgent(opts) {
1758
1813
  delete nextCtx.__resumeStage;
1759
1814
  state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
1760
1815
  }
1761
- const maxToolCalls = opts.limits?.maxToolCalls ?? 10;
1762
- const iterationLimit = Math.max(maxToolCalls * 3 + 10, 40);
1816
+ const maxToolCalls = opts.limits?.maxToolCalls === void 0 ? 50 : opts.limits?.maxToolCalls;
1817
+ const iterationLimit = maxToolCalls === Infinity ? 100 : Math.max(maxToolCalls * 3 + 10, 40);
1763
1818
  let iterations = 0;
1764
1819
  const onStateChange = config?.onStateChange;
1765
1820
  const checkpointReason = config?.checkpointReason;
@@ -1826,6 +1881,19 @@ function createAgent(opts) {
1826
1881
  }
1827
1882
  }
1828
1883
  }
1884
+ let maxTok = 5e4;
1885
+ if (opts.summarization && typeof opts.summarization === "object" && opts.summarization.maxTokens) {
1886
+ maxTok = opts.summarization.maxTokens;
1887
+ }
1888
+ if (maxTok) {
1889
+ 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");
1890
+ const tokenCount = countApproxTokens(allText);
1891
+ if (tokenCount > maxTok) {
1892
+ const ctx = { ...state.ctx || {}, __needsSummarization: true };
1893
+ state = { ...state, ctx };
1894
+ break;
1895
+ }
1896
+ }
1829
1897
  state = { ...state, ...await agentCore(state) };
1830
1898
  if (checkpointIfRequested("after_agent")) break;
1831
1899
  const postGuardrails = getGuardrailConfig(state);
@@ -1899,6 +1967,35 @@ function createAgent(opts) {
1899
1967
  if (checkpointIfRequested("after_tools")) break;
1900
1968
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
1901
1969
  }
1970
+ if (opts.outputSchema && !state.ctx?.__finalizedDueToStructuredOutput) {
1971
+ const last = state.messages[state.messages.length - 1];
1972
+ const lastHasToolCalls = Array.isArray(last?.tool_calls) && last.tool_calls.length > 0;
1973
+ if (!lastHasToolCalls) {
1974
+ const forceMsg = {
1975
+ role: "system",
1976
+ content: [
1977
+ "A structured output schema is active.",
1978
+ "You MUST now call tool `response` with the final JSON object that matches the schema.",
1979
+ "Do not write the JSON in the assistant message.",
1980
+ "Call `response` exactly once, then stop."
1981
+ ].join("\n")
1982
+ };
1983
+ const alreadyForced = Boolean(state.ctx?.__structuredOutputForceFinalize);
1984
+ if (!alreadyForced) {
1985
+ const ctx = { ...state.ctx || {}, __structuredOutputForceFinalize: true };
1986
+ state = { ...state, messages: [...state.messages, forceMsg], ctx };
1987
+ try {
1988
+ state = { ...state, ...await agentCore(state) };
1989
+ const lastAfter = state.messages[state.messages.length - 1];
1990
+ const toolCallsAfter = Array.isArray(lastAfter?.tool_calls) ? lastAfter.tool_calls : [];
1991
+ if (toolCallsAfter.length > 0) {
1992
+ state = { ...state, ...await toolsNode(state) };
1993
+ }
1994
+ } catch {
1995
+ }
1996
+ }
1997
+ }
1998
+ }
1902
1999
  if (!pausedStage && typeof onStateChange === "function" && onStateChange(state)) {
1903
2000
  checkpointIfRequested("after_loop");
1904
2001
  }
@@ -2133,234 +2230,96 @@ function createAgentNode(opts) {
2133
2230
  };
2134
2231
  }
2135
2232
 
2136
- // src/utils/utilTokens.ts
2137
- function countApproxTokens(text) {
2138
- if (!text) return 0;
2139
- return Math.ceil(text.length / 4);
2140
- }
2141
-
2142
2233
  // src/nodes/contextSummarize.ts
2143
2234
  var systemMessage = (content) => ({ role: "system", content });
2144
2235
  var humanMessage = (content) => ({ role: "user", content });
2145
- var toolMessage = (name, content, tool_call_id) => ({ role: "tool", name, content, tool_call_id });
2236
+ function getSummarizationConfig(opts) {
2237
+ if (typeof opts.summarization === "object") {
2238
+ return {
2239
+ enabled: opts.summarization.enable !== false,
2240
+ maxTokens: opts.summarization.maxTokens
2241
+ };
2242
+ }
2243
+ return {
2244
+ enabled: opts.summarization !== false,
2245
+ maxTokens: 5e4
2246
+ };
2247
+ }
2146
2248
  function createContextSummarizeNode(opts) {
2147
2249
  return async (state) => {
2148
- const messages = state.messages || [];
2149
- const summaryTokenLimit = opts.limits?.summaryTokenLimit ?? 1e5;
2250
+ const config = getSummarizationConfig(opts);
2251
+ if (!config.enabled) return {};
2150
2252
  const model = opts.model;
2151
- const archived = state.toolHistoryArchived ? [...state.toolHistoryArchived] : [];
2152
- const live = state.toolHistory ? [...state.toolHistory] : [];
2153
- const byCallId = /* @__PURE__ */ new Map();
2154
- for (const item of archived) {
2155
- if (item.tool_call_id) byCallId.set(item.tool_call_id, item);
2156
- }
2157
- for (const item of live) {
2158
- if (item.tool_call_id) byCallId.set(item.tool_call_id, item);
2159
- }
2160
- for (const item of live) {
2161
- if (!item.executionId) item.executionId = nanoid.nanoid();
2162
- }
2163
- const getText = (m) => {
2164
- if (!m) return "";
2165
- const c = m.content;
2166
- if (typeof c === "string") return c;
2167
- if (Array.isArray(c)) return c.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("");
2168
- return String(c ?? "");
2169
- };
2170
- 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.";
2171
- const sys = systemMessage(sysText);
2172
- let summaryText = "";
2173
- try {
2174
- const limit = Math.max(1e3, Number(summaryTokenLimit) || 1e5);
2175
- const safetyBuffer = Math.floor(Math.min(4e3, limit * 0.05));
2176
- const perCallBudget = Math.max(500, limit - safetyBuffer);
2177
- const safeToolPair = opts?.safeToolPair !== false;
2178
- const chunks = [];
2179
- let current = [];
2180
- let currentTokens = countApproxTokens(sysText);
2181
- let pendingToolCalls = /* @__PURE__ */ new Set();
2182
- const extractToolCallIds = (msg) => {
2183
- const tc = msg?.additional_kwargs?.tool_calls;
2184
- if (!Array.isArray(tc) || tc.length === 0) return [];
2185
- return tc.map((c) => c?.id).filter((id) => typeof id === "string");
2186
- };
2187
- for (let i = 0; i < messages.length; i++) {
2188
- const m = messages[i];
2189
- const text = getText(m);
2190
- const tks = countApproxTokens(text);
2191
- const isAIWithToolCalls = safeToolPair && m?.role === "assistant" && extractToolCallIds(m).length > 0;
2192
- const isToolMsg = safeToolPair && m?.role === "tool";
2193
- const toolCallId = isToolMsg ? m.tool_call_id : void 0;
2194
- const wouldExceed = currentTokens + tks > perCallBudget && current.length > 0;
2195
- if (wouldExceed) {
2196
- let forceAddToCurrent = false;
2197
- if (safeToolPair) {
2198
- if (isToolMsg && toolCallId && pendingToolCalls.has(toolCallId)) {
2199
- forceAddToCurrent = true;
2200
- } else if (isAIWithToolCalls) {
2201
- } else if (pendingToolCalls.size > 0) {
2202
- }
2203
- }
2204
- if (!forceAddToCurrent) {
2205
- if (current.length > 0) {
2206
- chunks.push(current);
2207
- }
2208
- current = [];
2209
- currentTokens = countApproxTokens(sysText);
2210
- pendingToolCalls = /* @__PURE__ */ new Set();
2211
- }
2212
- }
2213
- current.push(m);
2214
- currentTokens += tks;
2215
- if (isAIWithToolCalls) {
2216
- for (const id of extractToolCallIds(m)) pendingToolCalls.add(id);
2217
- }
2218
- if (isToolMsg && toolCallId && pendingToolCalls.has(toolCallId)) {
2219
- pendingToolCalls.delete(toolCallId);
2220
- }
2221
- if (currentTokens >= perCallBudget && pendingToolCalls.size === 0) {
2222
- chunks.push(current);
2223
- current = [];
2224
- currentTokens = countApproxTokens(sysText);
2225
- pendingToolCalls = /* @__PURE__ */ new Set();
2226
- }
2227
- }
2228
- if (current.length > 0) {
2229
- chunks.push(current);
2230
- }
2231
- const partials = [];
2232
- for (const chunk of chunks) {
2233
- let resp = null;
2234
- try {
2235
- resp = await (model?.invoke ? model.invoke([sys, ...chunk]) : null);
2236
- const c = resp?.content;
2237
- const text = typeof c === "string" ? c : Array.isArray(c) ? c.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("") : "";
2238
- partials.push((text || "").trim());
2239
- } catch (e) {
2240
- console.error("Summarization chunk failed:", e);
2241
- partials.push("[summary unavailable for one chunk]");
2242
- }
2253
+ const messages = state.messages || [];
2254
+ const conversationText = messages.map((m) => {
2255
+ let content = "";
2256
+ if (typeof m.content === "string") content = m.content;
2257
+ else if (Array.isArray(m.content)) {
2258
+ content = m.content.map((c) => c.text || JSON.stringify(c)).join(" ");
2243
2259
  }
2244
- const mergeSysText = "You merge partial summaries into a single cohesive, non-redundant summary that preserves key facts and decisions. Be concise.";
2245
- const mergeSys = systemMessage(mergeSysText);
2246
- const mergeIteratively = async (texts) => {
2247
- if (texts.length === 0) return "";
2248
- if (texts.length === 1) return texts[0];
2249
- const groups = [];
2250
- let group = [];
2251
- let toks = countApproxTokens(mergeSysText);
2252
- for (const t of texts) {
2253
- const tt = `- ${t}`;
2254
- const tk = countApproxTokens(tt);
2255
- if (toks + tk > perCallBudget && group.length > 0) {
2256
- groups.push(group);
2257
- group = [];
2258
- toks = countApproxTokens(mergeSysText);
2259
- }
2260
- group.push(tt);
2261
- toks += tk;
2262
- }
2263
- if (group.length > 0) groups.push(group);
2264
- const next = [];
2265
- for (const g of groups) {
2266
- const mergePrompt = humanMessage(`Merge the following partial summaries into a single concise brief. Avoid duplication, keep key facts and decisions.
2260
+ let prefix = `${m.role.toUpperCase()}`;
2261
+ if (m.name) prefix += ` (${m.name})`;
2262
+ return `${prefix}: ${content}`;
2263
+ }).join("\n\n");
2264
+ const summaryPrompt = [
2265
+ systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
2266
+ humanMessage(
2267
+ `Please summarize the following conversation history.
2268
+ Focus on:
2269
+ - User goals and intent.
2270
+ - Key decisions made and actions taken.
2271
+ - Important tool outputs and data retrieved.
2272
+ - Current state of the task.
2267
2273
 
2268
- ${g.join("\n")}
2274
+ Conversation:
2275
+ ${conversationText}
2269
2276
 
2270
- Merged summary:`);
2271
- try {
2272
- const r = await (model?.invoke ? model.invoke([mergeSys, mergePrompt]) : null);
2273
- const cc = r?.content;
2274
- const txt = typeof cc === "string" ? cc : Array.isArray(cc) ? cc.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("") : "";
2275
- next.push((txt || "").trim());
2276
- } catch (e) {
2277
- console.error("Merge summarization failed:", e);
2278
- next.push(g.join(" "));
2279
- }
2280
- }
2281
- return mergeIteratively(next);
2282
- };
2283
- summaryText = await mergeIteratively(partials);
2284
- if (!summaryText) summaryText = "Context summarized.";
2285
- } catch (e) {
2286
- console.error("Error during context summarization:", e);
2287
- summaryText = "Context summarized.";
2277
+ Summary:`
2278
+ )
2279
+ ];
2280
+ let summaryText = "Summary unavailable.";
2281
+ try {
2282
+ const response = await model.invoke(summaryPrompt);
2283
+ summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2284
+ } catch (err) {
2285
+ console.error("[ContextSummarize] Failed to generate summary:", err);
2286
+ return {};
2288
2287
  }
2289
- const rewritten = [];
2290
- const alreadySummarized = (content) => {
2291
- const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((p) => typeof p === "string" ? p : p?.text ?? p?.content ?? "").join("") : String(content ?? "");
2292
- return /\bSUMMARIZED\b/.test(text);
2293
- };
2294
- for (const m of messages) {
2295
- if (m.getType() === "tool") {
2296
- const toolMessageObj = m;
2297
- if (alreadySummarized(toolMessageObj.content) || toolMessageObj.name === "context_summarize" || toolMessageObj.name === "manage_todo_list") {
2298
- rewritten.push(m);
2299
- continue;
2300
- }
2301
- const callId = toolMessageObj.tool_call_id;
2302
- const safeId = callId || `summarized_${nanoid.nanoid(6)}`;
2303
- let hist = callId ? byCallId.get(callId) : void 0;
2304
- if (!hist) {
2305
- hist = {
2306
- executionId: nanoid.nanoid(),
2307
- toolName: toolMessageObj.name || "unknown_tool",
2308
- args: null,
2309
- 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 ?? ""),
2310
- rawOutput: void 0,
2311
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2312
- summarized: true,
2313
- messageId: void 0,
2314
- tool_call_id: callId,
2315
- fromCache: false
2316
- };
2317
- if (callId) byCallId.set(callId, hist);
2318
- archived.push({ ...hist });
2319
- }
2320
- if (!hist.executionId) hist.executionId = nanoid.nanoid();
2321
- hist.summarized = true;
2322
- rewritten.push({
2323
- role: "tool",
2324
- content: `SUMMARIZED executionId:'${hist.executionId}'`,
2325
- tool_call_id: safeId,
2326
- name: toolMessage.name
2327
- });
2328
- continue;
2288
+ const newMessages = messages.map((m) => {
2289
+ if (m.role === "tool") {
2290
+ if (m.content === "SUMMARIZED") return m;
2291
+ return {
2292
+ ...m,
2293
+ content: "SUMMARIZED"
2294
+ };
2329
2295
  }
2330
- rewritten.push(m);
2331
- }
2332
- const summarizeCallId = `summarize_${nanoid.nanoid(6)}`;
2333
- const summarizeArgs = { reason: "Exceeded token limit: summarize prior tool outputs for context." };
2334
- const syntheticAssistant = {
2296
+ return m;
2297
+ });
2298
+ const summaryToolCallId = `call_summary_${Date.now()}`;
2299
+ const assistantSummaryCall = {
2335
2300
  role: "assistant",
2336
- content: "",
2337
- additional_kwargs: {
2338
- tool_calls: [
2339
- { id: summarizeCallId, type: "function", function: { name: "context_summarize", arguments: JSON.stringify(summarizeArgs) } }
2340
- ]
2341
- }
2301
+ // "assistant"
2302
+ content: "Context limit reached. Summarizing conversation history to reduce token usage.",
2303
+ tool_calls: [
2304
+ {
2305
+ id: summaryToolCallId,
2306
+ type: "function",
2307
+ function: {
2308
+ name: "summarize_context",
2309
+ arguments: "{}"
2310
+ }
2311
+ }
2312
+ ]
2342
2313
  };
2343
- const syntheticToolResp = {
2314
+ const toolSummaryResponse = {
2344
2315
  role: "tool",
2345
- content: `${summaryText}`,
2346
- tool_call_id: summarizeCallId,
2347
- name: "context_summarize"
2316
+ tool_call_id: summaryToolCallId,
2317
+ name: "summarize_context",
2318
+ // Matches the tool name in the call
2319
+ content: summaryText
2348
2320
  };
2349
- for (const item of live) item.summarized = true;
2350
- const merged = [...archived, ...live];
2351
- const dedupMap = /* @__PURE__ */ new Map();
2352
- for (const it of merged) {
2353
- const key = it.tool_call_id || `${it.toolName}:${it.timestamp}`;
2354
- dedupMap.set(key, { ...it });
2355
- }
2356
- const newArchived = Array.from(dedupMap.values());
2357
- const onEvent = state.ctx?.__onEvent;
2358
- onEvent?.({ type: "summarization", summary: summaryText, archivedCount: newArchived.length });
2359
2321
  return {
2360
- messages: [...rewritten, syntheticAssistant, syntheticToolResp],
2361
- toolHistoryArchived: newArchived,
2362
- toolHistory: [],
2363
- ctx: { ...state.ctx || {}, __contextSummarized: true }
2322
+ messages: [...newMessages, assistantSummaryCall, toolSummaryResponse]
2364
2323
  };
2365
2324
  };
2366
2325
  }
@@ -2454,13 +2413,16 @@ function createContextTools(stateRef, opts) {
2454
2413
 
2455
2414
  // src/graph/decisions.ts
2456
2415
  function needsSummarization(state, opts, summarizationEnabled) {
2457
- const maxTok = opts.limits?.maxToken;
2458
- if (!maxTok || !summarizationEnabled) return false;
2416
+ const maxTok = opts.limits?.maxToken ?? opts.limits?.contextTokenLimit;
2417
+ if (!maxTok || !summarizationEnabled) {
2418
+ return false;
2419
+ }
2459
2420
  try {
2460
2421
  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");
2461
2422
  const tokenCount = countApproxTokens(allText);
2462
- return tokenCount > maxTok;
2463
- } catch {
2423
+ const needsSum = tokenCount > maxTok;
2424
+ return needsSum;
2425
+ } catch (e) {
2464
2426
  return false;
2465
2427
  }
2466
2428
  }
@@ -2485,7 +2447,12 @@ function createSmartAgent(opts) {
2485
2447
  const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
2486
2448
  const mergedTools = [...opts.tools ?? [], ...contextTools];
2487
2449
  const base = createAgent({ ...opts, tools: mergedTools });
2488
- const summarizationEnabled = opts.summarization !== false;
2450
+ let summarizationEnabled = false;
2451
+ if (typeof opts.summarization === "object") {
2452
+ summarizationEnabled = opts.summarization.enable !== false;
2453
+ } else {
2454
+ summarizationEnabled = opts.summarization !== false;
2455
+ }
2489
2456
  const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
2490
2457
  const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
2491
2458
  const decideAfter = toolsDecisionFactory(opts, summarizationEnabled);
@@ -2522,6 +2489,14 @@ function createSmartAgent(opts) {
2522
2489
  const res = await base.invoke(state, config);
2523
2490
  lastResult = res;
2524
2491
  state = res.state || { ...state, messages: res.messages };
2492
+ if (state.ctx?.__needsSummarization && summarizer) {
2493
+ const ctx = { ...state.ctx || {} };
2494
+ delete ctx.__needsSummarization;
2495
+ state = { ...state, ctx };
2496
+ const delta = await summarizer(state);
2497
+ state = { ...state, ...delta };
2498
+ continue;
2499
+ }
2525
2500
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
2526
2501
  if (summarizationEnabled) {
2527
2502
  const after = decideAfter(state);