@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/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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2140
|
-
|
|
2244
|
+
const config = getSummarizationConfig(opts);
|
|
2245
|
+
if (!config.enabled) return {};
|
|
2141
2246
|
const model = opts.model;
|
|
2142
|
-
const
|
|
2143
|
-
const
|
|
2144
|
-
|
|
2145
|
-
|
|
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
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
if (
|
|
2158
|
-
|
|
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
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
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
|
-
|
|
2272
|
+
Conversation:
|
|
2273
|
+
${conversationText}
|
|
2260
2274
|
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
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
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
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
|
-
|
|
2322
|
-
}
|
|
2323
|
-
const
|
|
2324
|
-
const
|
|
2325
|
-
const syntheticAssistant = {
|
|
2294
|
+
return m;
|
|
2295
|
+
});
|
|
2296
|
+
const summaryToolCallId = `call_summary_${Date.now()}`;
|
|
2297
|
+
const assistantSummaryCall = {
|
|
2326
2298
|
role: "assistant",
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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
|
|
2312
|
+
const toolSummaryResponse = {
|
|
2335
2313
|
role: "tool",
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
name
|
|
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: [...
|
|
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
|
-
|
|
2449
|
-
|
|
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
|
-
|
|
2454
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2511
|
-
|
|
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
|
-
|
|
2522
|
-
|
|
2513
|
+
if (delta && Object.keys(delta).length > 0) {
|
|
2514
|
+
state = { ...state, ...delta };
|
|
2515
|
+
continue;
|
|
2516
|
+
}
|
|
2523
2517
|
}
|
|
2524
2518
|
}
|
|
2525
2519
|
break;
|