@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 +21 -6
- package/dist/index.cjs +195 -220
- 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 +195 -220
- 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,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
|
-
|
|
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
|
|
2140
|
-
|
|
2241
|
+
const config = getSummarizationConfig(opts);
|
|
2242
|
+
if (!config.enabled) return {};
|
|
2141
2243
|
const model = opts.model;
|
|
2142
|
-
const
|
|
2143
|
-
const
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
if (
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
2265
|
+
Conversation:
|
|
2266
|
+
${conversationText}
|
|
2260
2267
|
|
|
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.";
|
|
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
|
|
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;
|
|
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
|
-
|
|
2322
|
-
}
|
|
2323
|
-
const
|
|
2324
|
-
const
|
|
2325
|
-
const syntheticAssistant = {
|
|
2287
|
+
return m;
|
|
2288
|
+
});
|
|
2289
|
+
const summaryToolCallId = `call_summary_${Date.now()}`;
|
|
2290
|
+
const assistantSummaryCall = {
|
|
2326
2291
|
role: "assistant",
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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
|
|
2305
|
+
const toolSummaryResponse = {
|
|
2335
2306
|
role: "tool",
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
name
|
|
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: [...
|
|
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)
|
|
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
|
-
|
|
2454
|
-
|
|
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
|
-
|
|
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);
|