@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/README.md +21 -6
- package/dist/index.cjs +217 -223
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -4
- package/dist/index.d.ts +16 -4
- package/dist/index.mjs +217 -223
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @cognipeer/agent-sdk
|
|
2
2
|
|
|
3
|
-
[](https://npmjs.com/package/@cognipeer/agent-sdk) [Docs Website](https://cognipeer.github.io/agent-sdk/)
|
|
3
|
+
[](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
|
-
|
|
161
|
+
To run examples:
|
|
162
162
|
|
|
163
|
-
```
|
|
164
|
-
#
|
|
165
|
-
npm
|
|
166
|
-
|
|
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
|
-
|
|
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
|
|
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,22 @@ function createAgent(opts) {
|
|
|
1826
1881
|
}
|
|
1827
1882
|
}
|
|
1828
1883
|
}
|
|
1884
|
+
let maxTok;
|
|
1885
|
+
if (opts.summarization && typeof opts.summarization === "object" && opts.summarization.maxTokens) {
|
|
1886
|
+
maxTok = opts.summarization.maxTokens;
|
|
1887
|
+
}
|
|
1888
|
+
if (maxTok === void 0) {
|
|
1889
|
+
maxTok = 5e4;
|
|
1890
|
+
}
|
|
1891
|
+
if (maxTok) {
|
|
1892
|
+
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");
|
|
1893
|
+
const tokenCount = countApproxTokens(allText);
|
|
1894
|
+
if (tokenCount > maxTok) {
|
|
1895
|
+
const ctx = { ...state.ctx || {}, __needsSummarization: true };
|
|
1896
|
+
state = { ...state, ctx };
|
|
1897
|
+
break;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1829
1900
|
state = { ...state, ...await agentCore(state) };
|
|
1830
1901
|
if (checkpointIfRequested("after_agent")) break;
|
|
1831
1902
|
const postGuardrails = getGuardrailConfig(state);
|
|
@@ -1899,6 +1970,35 @@ function createAgent(opts) {
|
|
|
1899
1970
|
if (checkpointIfRequested("after_tools")) break;
|
|
1900
1971
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
1901
1972
|
}
|
|
1973
|
+
if (opts.outputSchema && !state.ctx?.__finalizedDueToStructuredOutput) {
|
|
1974
|
+
const last = state.messages[state.messages.length - 1];
|
|
1975
|
+
const lastHasToolCalls = Array.isArray(last?.tool_calls) && last.tool_calls.length > 0;
|
|
1976
|
+
if (!lastHasToolCalls) {
|
|
1977
|
+
const forceMsg = {
|
|
1978
|
+
role: "system",
|
|
1979
|
+
content: [
|
|
1980
|
+
"A structured output schema is active.",
|
|
1981
|
+
"You MUST now call tool `response` with the final JSON object that matches the schema.",
|
|
1982
|
+
"Do not write the JSON in the assistant message.",
|
|
1983
|
+
"Call `response` exactly once, then stop."
|
|
1984
|
+
].join("\n")
|
|
1985
|
+
};
|
|
1986
|
+
const alreadyForced = Boolean(state.ctx?.__structuredOutputForceFinalize);
|
|
1987
|
+
if (!alreadyForced) {
|
|
1988
|
+
const ctx = { ...state.ctx || {}, __structuredOutputForceFinalize: true };
|
|
1989
|
+
state = { ...state, messages: [...state.messages, forceMsg], ctx };
|
|
1990
|
+
try {
|
|
1991
|
+
state = { ...state, ...await agentCore(state) };
|
|
1992
|
+
const lastAfter = state.messages[state.messages.length - 1];
|
|
1993
|
+
const toolCallsAfter = Array.isArray(lastAfter?.tool_calls) ? lastAfter.tool_calls : [];
|
|
1994
|
+
if (toolCallsAfter.length > 0) {
|
|
1995
|
+
state = { ...state, ...await toolsNode(state) };
|
|
1996
|
+
}
|
|
1997
|
+
} catch {
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
1902
2002
|
if (!pausedStage && typeof onStateChange === "function" && onStateChange(state)) {
|
|
1903
2003
|
checkpointIfRequested("after_loop");
|
|
1904
2004
|
}
|
|
@@ -2133,234 +2233,100 @@ function createAgentNode(opts) {
|
|
|
2133
2233
|
};
|
|
2134
2234
|
}
|
|
2135
2235
|
|
|
2136
|
-
// src/utils/utilTokens.ts
|
|
2137
|
-
function countApproxTokens(text) {
|
|
2138
|
-
if (!text) return 0;
|
|
2139
|
-
return Math.ceil(text.length / 4);
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
2236
|
// src/nodes/contextSummarize.ts
|
|
2143
2237
|
var systemMessage = (content) => ({ role: "system", content });
|
|
2144
2238
|
var humanMessage = (content) => ({ role: "user", content });
|
|
2145
|
-
|
|
2239
|
+
function getSummarizationConfig(opts) {
|
|
2240
|
+
if (typeof opts.summarization === "object") {
|
|
2241
|
+
return {
|
|
2242
|
+
enabled: opts.summarization.enable !== false,
|
|
2243
|
+
maxTokens: opts.summarization.maxTokens
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
return {
|
|
2247
|
+
enabled: opts.summarization !== false,
|
|
2248
|
+
maxTokens: 5e4
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2146
2251
|
function createContextSummarizeNode(opts) {
|
|
2147
2252
|
return async (state) => {
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2253
|
+
const config = getSummarizationConfig(opts);
|
|
2254
|
+
if (!config.enabled) return {};
|
|
2150
2255
|
const model = opts.model;
|
|
2151
|
-
const
|
|
2152
|
-
const
|
|
2153
|
-
|
|
2154
|
-
|
|
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();
|
|
2256
|
+
const messages = state.messages || [];
|
|
2257
|
+
const compressableMessages = messages.filter((m) => m.role === "tool" && m.content !== "SUMMARIZED");
|
|
2258
|
+
if (compressableMessages.length === 0) {
|
|
2259
|
+
return {};
|
|
2162
2260
|
}
|
|
2163
|
-
const
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
if (
|
|
2167
|
-
|
|
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
|
-
}
|
|
2261
|
+
const conversationText = messages.map((m) => {
|
|
2262
|
+
let content = "";
|
|
2263
|
+
if (typeof m.content === "string") content = m.content;
|
|
2264
|
+
else if (Array.isArray(m.content)) {
|
|
2265
|
+
content = m.content.map((c) => c.text || JSON.stringify(c)).join(" ");
|
|
2243
2266
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
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.
|
|
2267
|
+
let prefix = `${m.role.toUpperCase()}`;
|
|
2268
|
+
if (m.name) prefix += ` (${m.name})`;
|
|
2269
|
+
return `${prefix}: ${content}`;
|
|
2270
|
+
}).join("\n\n");
|
|
2271
|
+
const summaryPrompt = [
|
|
2272
|
+
systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
|
|
2273
|
+
humanMessage(
|
|
2274
|
+
`Please summarize the following conversation history.
|
|
2275
|
+
Focus on:
|
|
2276
|
+
- User goals and intent.
|
|
2277
|
+
- Key decisions made and actions taken.
|
|
2278
|
+
- Important tool outputs and data retrieved.
|
|
2279
|
+
- Current state of the task.
|
|
2267
2280
|
|
|
2268
|
-
|
|
2281
|
+
Conversation:
|
|
2282
|
+
${conversationText}
|
|
2269
2283
|
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
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.";
|
|
2284
|
+
Summary:`
|
|
2285
|
+
)
|
|
2286
|
+
];
|
|
2287
|
+
let summaryText = "Summary unavailable.";
|
|
2288
|
+
try {
|
|
2289
|
+
const response = await model.invoke(summaryPrompt);
|
|
2290
|
+
summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
|
|
2291
|
+
} catch (err) {
|
|
2292
|
+
console.error("[ContextSummarize] Failed to generate summary:", err);
|
|
2293
|
+
return {};
|
|
2288
2294
|
}
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
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;
|
|
2295
|
+
const newMessages = messages.map((m) => {
|
|
2296
|
+
if (m.role === "tool") {
|
|
2297
|
+
if (m.content === "SUMMARIZED") return m;
|
|
2298
|
+
return {
|
|
2299
|
+
...m,
|
|
2300
|
+
content: "SUMMARIZED"
|
|
2301
|
+
};
|
|
2329
2302
|
}
|
|
2330
|
-
|
|
2331
|
-
}
|
|
2332
|
-
const
|
|
2333
|
-
const
|
|
2334
|
-
const syntheticAssistant = {
|
|
2303
|
+
return m;
|
|
2304
|
+
});
|
|
2305
|
+
const summaryToolCallId = `call_summary_${Date.now()}`;
|
|
2306
|
+
const assistantSummaryCall = {
|
|
2335
2307
|
role: "assistant",
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2308
|
+
// "assistant"
|
|
2309
|
+
content: "Context limit reached. Summarizing conversation history to reduce token usage.",
|
|
2310
|
+
tool_calls: [
|
|
2311
|
+
{
|
|
2312
|
+
id: summaryToolCallId,
|
|
2313
|
+
type: "function",
|
|
2314
|
+
function: {
|
|
2315
|
+
name: "summarize_context",
|
|
2316
|
+
arguments: "{}"
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
]
|
|
2342
2320
|
};
|
|
2343
|
-
const
|
|
2321
|
+
const toolSummaryResponse = {
|
|
2344
2322
|
role: "tool",
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
name
|
|
2323
|
+
tool_call_id: summaryToolCallId,
|
|
2324
|
+
name: "summarize_context",
|
|
2325
|
+
// Matches the tool name in the call
|
|
2326
|
+
content: summaryText
|
|
2348
2327
|
};
|
|
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
2328
|
return {
|
|
2360
|
-
messages: [...
|
|
2361
|
-
toolHistoryArchived: newArchived,
|
|
2362
|
-
toolHistory: [],
|
|
2363
|
-
ctx: { ...state.ctx || {}, __contextSummarized: true }
|
|
2329
|
+
messages: [...newMessages, assistantSummaryCall, toolSummaryResponse]
|
|
2364
2330
|
};
|
|
2365
2331
|
};
|
|
2366
2332
|
}
|
|
@@ -2454,13 +2420,22 @@ function createContextTools(stateRef, opts) {
|
|
|
2454
2420
|
|
|
2455
2421
|
// src/graph/decisions.ts
|
|
2456
2422
|
function needsSummarization(state, opts, summarizationEnabled) {
|
|
2457
|
-
|
|
2458
|
-
|
|
2423
|
+
if (!summarizationEnabled) {
|
|
2424
|
+
return false;
|
|
2425
|
+
}
|
|
2426
|
+
let maxTok;
|
|
2427
|
+
if (typeof opts.summarization === "object" && typeof opts.summarization.maxTokens === "number") {
|
|
2428
|
+
maxTok = opts.summarization.maxTokens;
|
|
2429
|
+
}
|
|
2430
|
+
if (maxTok === void 0) {
|
|
2431
|
+
maxTok = 5e4;
|
|
2432
|
+
}
|
|
2459
2433
|
try {
|
|
2460
2434
|
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
2435
|
const tokenCount = countApproxTokens(allText);
|
|
2462
|
-
|
|
2463
|
-
|
|
2436
|
+
const needsSum = tokenCount > maxTok;
|
|
2437
|
+
return needsSum;
|
|
2438
|
+
} catch (e) {
|
|
2464
2439
|
return false;
|
|
2465
2440
|
}
|
|
2466
2441
|
}
|
|
@@ -2485,7 +2460,12 @@ function createSmartAgent(opts) {
|
|
|
2485
2460
|
const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
|
|
2486
2461
|
const mergedTools = [...opts.tools ?? [], ...contextTools];
|
|
2487
2462
|
const base = createAgent({ ...opts, tools: mergedTools });
|
|
2488
|
-
|
|
2463
|
+
let summarizationEnabled = false;
|
|
2464
|
+
if (typeof opts.summarization === "object") {
|
|
2465
|
+
summarizationEnabled = opts.summarization.enable !== false;
|
|
2466
|
+
} else {
|
|
2467
|
+
summarizationEnabled = opts.summarization !== false;
|
|
2468
|
+
}
|
|
2489
2469
|
const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
|
|
2490
2470
|
const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
|
|
2491
2471
|
const decideAfter = toolsDecisionFactory(opts, summarizationEnabled);
|
|
@@ -2516,19 +2496,33 @@ function createSmartAgent(opts) {
|
|
|
2516
2496
|
const next = summarizationEnabled ? decideBefore(state) : "agent";
|
|
2517
2497
|
if (next === "contextSummarize" && summarizer) {
|
|
2518
2498
|
const delta = await summarizer(state);
|
|
2519
|
-
|
|
2520
|
-
|
|
2499
|
+
if (!delta || Object.keys(delta).length === 0) ; else {
|
|
2500
|
+
state = { ...state, ...delta };
|
|
2501
|
+
continue;
|
|
2502
|
+
}
|
|
2521
2503
|
}
|
|
2522
2504
|
const res = await base.invoke(state, config);
|
|
2523
2505
|
lastResult = res;
|
|
2524
2506
|
state = res.state || { ...state, messages: res.messages };
|
|
2507
|
+
if (state.ctx?.__needsSummarization && summarizer) {
|
|
2508
|
+
const ctx = { ...state.ctx || {} };
|
|
2509
|
+
delete ctx.__needsSummarization;
|
|
2510
|
+
state = { ...state, ctx };
|
|
2511
|
+
const delta = await summarizer(state);
|
|
2512
|
+
if (delta && Object.keys(delta).length > 0) {
|
|
2513
|
+
state = { ...state, ...delta };
|
|
2514
|
+
}
|
|
2515
|
+
continue;
|
|
2516
|
+
}
|
|
2525
2517
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
2526
2518
|
if (summarizationEnabled) {
|
|
2527
2519
|
const after = decideAfter(state);
|
|
2528
2520
|
if (after === "contextSummarize" && summarizer) {
|
|
2529
2521
|
const delta = await summarizer(state);
|
|
2530
|
-
|
|
2531
|
-
|
|
2522
|
+
if (delta && Object.keys(delta).length > 0) {
|
|
2523
|
+
state = { ...state, ...delta };
|
|
2524
|
+
continue;
|
|
2525
|
+
}
|
|
2532
2526
|
}
|
|
2533
2527
|
}
|
|
2534
2528
|
break;
|