@cognipeer/agent-sdk 0.3.1 → 0.4.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 +36 -9
- package/dist/index.cjs +1934 -290
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -35
- package/dist/index.d.ts +341 -35
- package/dist/index.mjs +1922 -292
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ var fs = require('fs');
|
|
|
6
6
|
var path = require('path');
|
|
7
7
|
var buffer = require('buffer');
|
|
8
8
|
var nanoid = require('nanoid');
|
|
9
|
+
var crypto = require('crypto');
|
|
9
10
|
var AjvModule = require('ajv');
|
|
10
11
|
|
|
11
12
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -13,6 +14,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
13
14
|
|
|
14
15
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
15
16
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
17
|
+
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
16
18
|
var AjvModule__default = /*#__PURE__*/_interopDefault(AjvModule);
|
|
17
19
|
|
|
18
20
|
// src/adapters/langchain.ts
|
|
@@ -224,7 +226,8 @@ function createTool({
|
|
|
224
226
|
func,
|
|
225
227
|
needsApproval,
|
|
226
228
|
approvalPrompt,
|
|
227
|
-
approvalDefaults
|
|
229
|
+
approvalDefaults,
|
|
230
|
+
maxExecutionsPerRun
|
|
228
231
|
}) {
|
|
229
232
|
const execute = async (input) => func(input);
|
|
230
233
|
const toolRecord = {
|
|
@@ -246,25 +249,45 @@ function createTool({
|
|
|
246
249
|
if (approvalDefaults !== void 0) {
|
|
247
250
|
toolRecord.approvalDefaults = approvalDefaults;
|
|
248
251
|
}
|
|
252
|
+
if (maxExecutionsPerRun !== void 0) {
|
|
253
|
+
toolRecord.maxExecutionsPerRun = maxExecutionsPerRun;
|
|
254
|
+
}
|
|
249
255
|
toolRecord.__source = toolRecord.__source || "smart";
|
|
250
256
|
toolRecord.__impl = func;
|
|
251
257
|
return toolRecord;
|
|
252
258
|
}
|
|
253
259
|
|
|
254
260
|
// src/prompts.ts
|
|
255
|
-
|
|
256
|
-
const extraTrimmed = extra?.trim();
|
|
257
|
-
const agentHeader = `Agent Name: ${name}`;
|
|
258
|
-
const planningBlock = planning ? `<planning>
|
|
259
|
-
PLANNING IS MANDATORY.
|
|
261
|
+
var DEFAULT_TODO_LIST_PROMPT = `Planning tools are available when they materially improve execution.
|
|
260
262
|
|
|
261
263
|
Rules:
|
|
262
|
-
1)
|
|
263
|
-
2)
|
|
264
|
-
3)
|
|
265
|
-
4)
|
|
266
|
-
5)
|
|
267
|
-
|
|
264
|
+
1) Use "manage_todo_list" for multi-step work, delegation, recovery after a failed attempt, or when the user explicitly asks for a plan.
|
|
265
|
+
2) Do NOT create a plan for direct Q&A, simple recall from existing context, or a single straightforward tool lookup unless the user asks for one.
|
|
266
|
+
3) If a task is multi-step and no valid plan exists yet, create one before substantial execution.
|
|
267
|
+
4) Use operation="write" only to create or fully replace the entire plan. After a plan exists, default to operation="update" for progress, evidence, status, owner changes, blockers, or reprioritization.
|
|
268
|
+
5) When using operation="update", send only the changed items. Do not resend unchanged plan items unless the whole plan structure is being rewritten.
|
|
269
|
+
6) Include expectedVersion from the latest successful plan state whenever you update an existing plan. If you do not know the latest version, read the plan first.
|
|
270
|
+
7) Keep exactly ONE item "in-progress" at a time.
|
|
271
|
+
8) Update the plan whenever a step starts, completes, becomes blocked, fails, or the approach changes. Do not finish a multi-step task with stale plan state.
|
|
272
|
+
9) If tool results materially change the task state, sync the plan before the final answer.
|
|
273
|
+
10) Reuse prior tool results already present in the conversation before calling tools again.
|
|
274
|
+
11) If an update fails because of a version mismatch, read the latest plan and retry instead of blindly rewriting it.
|
|
275
|
+
12) Repeating full operation="write" calls for the same plan is a mistake unless the plan structure itself changed.
|
|
276
|
+
13) Keep the plan internal unless the user explicitly asks to see it.`;
|
|
277
|
+
function buildPlanningBlock(todoListPrompt) {
|
|
278
|
+
const promptBody = todoListPrompt?.trim() || DEFAULT_TODO_LIST_PROMPT;
|
|
279
|
+
if (promptBody.startsWith("<planning>")) {
|
|
280
|
+
return promptBody;
|
|
281
|
+
}
|
|
282
|
+
return `<planning>
|
|
283
|
+
${promptBody}
|
|
284
|
+
</planning>`;
|
|
285
|
+
}
|
|
286
|
+
function buildSystemPrompt(extra, planning, name = "Agent", todoListPrompt) {
|
|
287
|
+
const extraTrimmed = extra?.trim();
|
|
288
|
+
const agentHeader = `Agent Name: ${name}`;
|
|
289
|
+
const planningMode = typeof planning === "string" ? planning : planning ? "todo" : "off";
|
|
290
|
+
const planningBlock = planningMode !== "off" ? buildPlanningBlock(todoListPrompt) : "";
|
|
268
291
|
return [
|
|
269
292
|
agentHeader,
|
|
270
293
|
"You are an advanced AI agent that is concise, accurate, and helpful.",
|
|
@@ -376,6 +399,9 @@ function cognipeerSink(first, second) {
|
|
|
376
399
|
function httpSink(url, headers) {
|
|
377
400
|
return { type: "http", url, headers };
|
|
378
401
|
}
|
|
402
|
+
function otlpSink(endpoint, headers) {
|
|
403
|
+
return { type: "otlp", endpoint, headers };
|
|
404
|
+
}
|
|
379
405
|
function resolveLogsBaseDir(customPath, ensureDirectory = true) {
|
|
380
406
|
const root = process.cwd();
|
|
381
407
|
const base = customPath && customPath.trim().length > 0 ? customPath : path__default.default.join(root, "logs");
|
|
@@ -575,6 +601,14 @@ function resolveSink(sink) {
|
|
|
575
601
|
const headers = candidate.headers ? { ...candidate.headers } : void 0;
|
|
576
602
|
return headers ? { type: "http", url, headers } : { type: "http", url };
|
|
577
603
|
}
|
|
604
|
+
case "otlp": {
|
|
605
|
+
const endpoint = typeof candidate.endpoint === "string" ? candidate.endpoint.trim() : "";
|
|
606
|
+
if (!endpoint) {
|
|
607
|
+
throw new Error("otlp sink requires a non-empty endpoint");
|
|
608
|
+
}
|
|
609
|
+
const headers = candidate.headers ? { ...candidate.headers } : void 0;
|
|
610
|
+
return headers ? { type: "otlp", endpoint, headers } : { type: "otlp", endpoint };
|
|
611
|
+
}
|
|
578
612
|
default: {
|
|
579
613
|
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
580
614
|
console.warn("Unknown tracing sink type. Falling back to file sink.", candidate);
|
|
@@ -594,6 +628,8 @@ function snapshotSink(runtime) {
|
|
|
594
628
|
return { type: "cognipeer", url: sink.url };
|
|
595
629
|
case "http":
|
|
596
630
|
return { type: "http", url: sink.url };
|
|
631
|
+
case "otlp":
|
|
632
|
+
return { type: "otlp", endpoint: sink.endpoint };
|
|
597
633
|
default:
|
|
598
634
|
return { type: "custom" };
|
|
599
635
|
}
|
|
@@ -744,6 +780,202 @@ async function postStreamingSessionEnd(baseUrl, headers, payload) {
|
|
|
744
780
|
throw err;
|
|
745
781
|
}
|
|
746
782
|
}
|
|
783
|
+
function isoToUnixNano(iso) {
|
|
784
|
+
const ms = new Date(iso).getTime();
|
|
785
|
+
return `${ms}000000`;
|
|
786
|
+
}
|
|
787
|
+
function toOtlpStatusCode(status) {
|
|
788
|
+
if (status === "error") return 2;
|
|
789
|
+
if (status === "success" || status === "completed") return 1;
|
|
790
|
+
return 0;
|
|
791
|
+
}
|
|
792
|
+
function toOtlpSpanKind(type) {
|
|
793
|
+
switch (type) {
|
|
794
|
+
case "ai_call":
|
|
795
|
+
return 3;
|
|
796
|
+
// CLIENT — outgoing LLM request
|
|
797
|
+
case "tool_call":
|
|
798
|
+
return 1;
|
|
799
|
+
// INTERNAL
|
|
800
|
+
case "agent_iteration":
|
|
801
|
+
return 1;
|
|
802
|
+
// INTERNAL
|
|
803
|
+
default:
|
|
804
|
+
return 1;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
function stringAttr(key, val) {
|
|
808
|
+
return val != null ? { key, value: { stringValue: val } } : null;
|
|
809
|
+
}
|
|
810
|
+
function intAttr(key, val) {
|
|
811
|
+
return val != null && !Number.isNaN(val) ? { key, value: { intValue: String(val) } } : null;
|
|
812
|
+
}
|
|
813
|
+
function doubleAttr(key, val) {
|
|
814
|
+
return val != null && !Number.isNaN(val) ? { key, value: { doubleValue: val } } : null;
|
|
815
|
+
}
|
|
816
|
+
function eventToOtlpSpan(event) {
|
|
817
|
+
const startNano = isoToUnixNano(event.timestamp);
|
|
818
|
+
const endNano = event.durationMs != null ? `${new Date(new Date(event.timestamp).getTime() + event.durationMs).getTime()}000000` : startNano;
|
|
819
|
+
const attrs = [
|
|
820
|
+
stringAttr("cognipeer.event.type", event.type),
|
|
821
|
+
stringAttr("cognipeer.event.label", event.label),
|
|
822
|
+
intAttr("cognipeer.event.sequence", event.sequence),
|
|
823
|
+
stringAttr("cognipeer.actor.scope", event.actor?.scope),
|
|
824
|
+
stringAttr("cognipeer.actor.name", event.actor?.name),
|
|
825
|
+
stringAttr("cognipeer.actor.role", event.actor?.role),
|
|
826
|
+
stringAttr("cognipeer.model", event.model),
|
|
827
|
+
stringAttr("cognipeer.provider", event.provider),
|
|
828
|
+
intAttr("cognipeer.tokens.input", event.inputTokens),
|
|
829
|
+
intAttr("cognipeer.tokens.output", event.outputTokens),
|
|
830
|
+
intAttr("cognipeer.tokens.total", event.totalTokens),
|
|
831
|
+
intAttr("cognipeer.tokens.cached_input", event.cachedInputTokens),
|
|
832
|
+
intAttr("cognipeer.bytes.request", event.requestBytes),
|
|
833
|
+
intAttr("cognipeer.bytes.response", event.responseBytes),
|
|
834
|
+
stringAttr("cognipeer.tool.execution_id", event.toolExecutionId),
|
|
835
|
+
stringAttr("cognipeer.retry_of", event.retryOf),
|
|
836
|
+
stringAttr("cognipeer.event.id", event.id)
|
|
837
|
+
].filter(Boolean);
|
|
838
|
+
if (event.data?.sections && event.data.sections.length > 0) {
|
|
839
|
+
try {
|
|
840
|
+
attrs.push({ key: "cognipeer.sections", value: { stringValue: JSON.stringify(event.data.sections) } });
|
|
841
|
+
} catch {
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const span = {
|
|
845
|
+
traceId: event.traceId,
|
|
846
|
+
spanId: event.spanId,
|
|
847
|
+
parentSpanId: event.parentSpanId || "",
|
|
848
|
+
name: event.label || event.type,
|
|
849
|
+
kind: toOtlpSpanKind(event.type),
|
|
850
|
+
startTimeUnixNano: startNano,
|
|
851
|
+
endTimeUnixNano: endNano,
|
|
852
|
+
attributes: attrs,
|
|
853
|
+
status: {
|
|
854
|
+
code: toOtlpStatusCode(event.status),
|
|
855
|
+
...event.status === "error" && event.error?.message ? { message: event.error.message } : {}
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
if (event.error) {
|
|
859
|
+
span.events = [{
|
|
860
|
+
timeUnixNano: startNano,
|
|
861
|
+
name: "exception",
|
|
862
|
+
attributes: [
|
|
863
|
+
{ key: "exception.message", value: { stringValue: event.error.message } },
|
|
864
|
+
...event.error.stack ? [{ key: "exception.stacktrace", value: { stringValue: event.error.stack } }] : []
|
|
865
|
+
]
|
|
866
|
+
}];
|
|
867
|
+
}
|
|
868
|
+
return span;
|
|
869
|
+
}
|
|
870
|
+
function traceSessionToOtlp(session) {
|
|
871
|
+
const traceId = session.traceId || generateTraceId();
|
|
872
|
+
const rootSpanId = session.rootSpanId || generateSpanId();
|
|
873
|
+
const startNano = isoToUnixNano(session.startedAt);
|
|
874
|
+
const endNano = session.endedAt ? isoToUnixNano(session.endedAt) : startNano;
|
|
875
|
+
const resourceAttrs = [
|
|
876
|
+
{ key: "service.name", value: { stringValue: session.agent?.name || "cognipeer-agent" } },
|
|
877
|
+
stringAttr("service.version", session.agent?.version),
|
|
878
|
+
stringAttr("cognipeer.session.id", session.sessionId),
|
|
879
|
+
stringAttr("cognipeer.thread.id", session.threadId),
|
|
880
|
+
stringAttr("cognipeer.agent.model", session.agent?.model),
|
|
881
|
+
stringAttr("cognipeer.agent.provider", session.agent?.provider)
|
|
882
|
+
].filter(Boolean);
|
|
883
|
+
const rootAttrs = [
|
|
884
|
+
stringAttr("cognipeer.session.status", session.status),
|
|
885
|
+
doubleAttr("cognipeer.session.duration_ms", session.durationMs),
|
|
886
|
+
intAttr("cognipeer.session.total_input_tokens", session.summary.totalInputTokens),
|
|
887
|
+
intAttr("cognipeer.session.total_output_tokens", session.summary.totalOutputTokens),
|
|
888
|
+
intAttr("cognipeer.session.total_cached_input_tokens", session.summary.totalCachedInputTokens),
|
|
889
|
+
intAttr("cognipeer.session.total_bytes_in", session.summary.totalBytesIn),
|
|
890
|
+
intAttr("cognipeer.session.total_bytes_out", session.summary.totalBytesOut),
|
|
891
|
+
intAttr("cognipeer.session.event_count", session.events.length)
|
|
892
|
+
].filter(Boolean);
|
|
893
|
+
if (session.summary.eventCounts) {
|
|
894
|
+
for (const [key, count] of Object.entries(session.summary.eventCounts)) {
|
|
895
|
+
rootAttrs.push({ key: `cognipeer.session.event_count.${key}`, value: { intValue: String(count) } });
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const rootSpan = {
|
|
899
|
+
traceId,
|
|
900
|
+
spanId: rootSpanId,
|
|
901
|
+
parentSpanId: "",
|
|
902
|
+
name: `agent_session: ${session.agent?.name || "agent"}`,
|
|
903
|
+
kind: 1,
|
|
904
|
+
// INTERNAL
|
|
905
|
+
startTimeUnixNano: startNano,
|
|
906
|
+
endTimeUnixNano: endNano,
|
|
907
|
+
attributes: rootAttrs,
|
|
908
|
+
status: {
|
|
909
|
+
code: toOtlpStatusCode(session.status),
|
|
910
|
+
...session.status === "error" && session.errors.length > 0 ? { message: session.errors.map((e) => e.message).join("; ") } : {}
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
if (session.errors.length > 0) {
|
|
914
|
+
rootSpan.events = session.errors.map((err) => ({
|
|
915
|
+
timeUnixNano: err.timestamp ? isoToUnixNano(err.timestamp) : startNano,
|
|
916
|
+
name: "exception",
|
|
917
|
+
attributes: [
|
|
918
|
+
{ key: "exception.message", value: { stringValue: err.message } },
|
|
919
|
+
...err.stack ? [{ key: "exception.stacktrace", value: { stringValue: err.stack } }] : [],
|
|
920
|
+
...err.type ? [{ key: "exception.type", value: { stringValue: err.type } }] : []
|
|
921
|
+
]
|
|
922
|
+
}));
|
|
923
|
+
}
|
|
924
|
+
const childSpans = session.events.map((event) => {
|
|
925
|
+
const span = eventToOtlpSpan({
|
|
926
|
+
...event,
|
|
927
|
+
traceId: event.traceId || traceId,
|
|
928
|
+
spanId: event.spanId || generateSpanId(),
|
|
929
|
+
parentSpanId: event.parentSpanId || rootSpanId
|
|
930
|
+
});
|
|
931
|
+
return span;
|
|
932
|
+
});
|
|
933
|
+
return {
|
|
934
|
+
resourceSpans: [{
|
|
935
|
+
resource: { attributes: resourceAttrs },
|
|
936
|
+
scopeSpans: [{
|
|
937
|
+
scope: {
|
|
938
|
+
name: "cognipeer-agent-sdk",
|
|
939
|
+
version: "1.0.0"
|
|
940
|
+
},
|
|
941
|
+
spans: [rootSpan, ...childSpans]
|
|
942
|
+
}]
|
|
943
|
+
}]
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
async function postOtlpTraces(endpoint, headers, payload) {
|
|
947
|
+
const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
|
|
948
|
+
if (!fetchFn) {
|
|
949
|
+
throw new Error("OTLP sink requires fetch to be available in this runtime.");
|
|
950
|
+
}
|
|
951
|
+
const finalHeaders = {
|
|
952
|
+
"content-type": "application/json",
|
|
953
|
+
...headers || {}
|
|
954
|
+
};
|
|
955
|
+
try {
|
|
956
|
+
const response = await fetchFn(endpoint, {
|
|
957
|
+
method: "POST",
|
|
958
|
+
headers: finalHeaders,
|
|
959
|
+
body: JSON.stringify(payload)
|
|
960
|
+
});
|
|
961
|
+
if (!response.ok) {
|
|
962
|
+
let responseText = "";
|
|
963
|
+
try {
|
|
964
|
+
responseText = await response.text();
|
|
965
|
+
} catch {
|
|
966
|
+
}
|
|
967
|
+
const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
|
|
968
|
+
const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
|
|
969
|
+
throw new Error(`${statusLine}${bodyPreview}`);
|
|
970
|
+
}
|
|
971
|
+
} catch (err) {
|
|
972
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
973
|
+
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
974
|
+
console.error("[Tracing] OTLP export failed:", errMsg);
|
|
975
|
+
}
|
|
976
|
+
throw err;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
747
979
|
function createEmptySummary() {
|
|
748
980
|
return {
|
|
749
981
|
totalDurationMs: 0,
|
|
@@ -761,6 +993,12 @@ function generateSessionId() {
|
|
|
761
993
|
function generateEventId(sequence) {
|
|
762
994
|
return `evt_${String(sequence).padStart(4, "0")}_${nanoid.nanoid(4)}`;
|
|
763
995
|
}
|
|
996
|
+
function generateTraceId() {
|
|
997
|
+
return crypto__default.default.randomBytes(16).toString("hex");
|
|
998
|
+
}
|
|
999
|
+
function generateSpanId() {
|
|
1000
|
+
return crypto__default.default.randomBytes(8).toString("hex");
|
|
1001
|
+
}
|
|
764
1002
|
function defaultEventLabel(type) {
|
|
765
1003
|
switch (type) {
|
|
766
1004
|
case "ai_call":
|
|
@@ -913,8 +1151,12 @@ function createTraceSession(opts) {
|
|
|
913
1151
|
const cfg = withDefaults(opts.tracing);
|
|
914
1152
|
if (!cfg.enabled) return void 0;
|
|
915
1153
|
const sessionId = generateSessionId();
|
|
1154
|
+
const traceId = generateTraceId();
|
|
1155
|
+
const rootSpanId = generateSpanId();
|
|
916
1156
|
const runtime = {
|
|
917
1157
|
sessionId,
|
|
1158
|
+
traceId,
|
|
1159
|
+
rootSpanId,
|
|
918
1160
|
threadId: opts.tracing?.threadId,
|
|
919
1161
|
startedAt: Date.now(),
|
|
920
1162
|
resolvedConfig: cfg,
|
|
@@ -1024,8 +1266,13 @@ function recordTraceEvent(session, event) {
|
|
|
1024
1266
|
const resolvedModel = event.model ?? inferModelFromMessages(event.messageList);
|
|
1025
1267
|
const resolvedProvider = event.provider ?? inferProviderFromMessages(event.messageList);
|
|
1026
1268
|
const isAiCall = event.type === "ai_call";
|
|
1269
|
+
const spanId = generateSpanId();
|
|
1270
|
+
const parentSpanId = event.parentSpanId || session.currentIterationSpanId || session.rootSpanId;
|
|
1027
1271
|
const record = {
|
|
1028
1272
|
sessionId: session.sessionId,
|
|
1273
|
+
traceId: session.traceId,
|
|
1274
|
+
spanId,
|
|
1275
|
+
parentSpanId,
|
|
1029
1276
|
id,
|
|
1030
1277
|
type: event.type,
|
|
1031
1278
|
label: eventLabel,
|
|
@@ -1132,6 +1379,8 @@ async function finalizeTraceSession(session, params = {}) {
|
|
|
1132
1379
|
const initialStatus = params.status ? params.status : session.errors.length > 0 ? "error" : "success";
|
|
1133
1380
|
const buildPayload = (status2) => ({
|
|
1134
1381
|
sessionId: session.sessionId,
|
|
1382
|
+
traceId: session.traceId,
|
|
1383
|
+
rootSpanId: session.rootSpanId,
|
|
1135
1384
|
threadId: session.threadId,
|
|
1136
1385
|
startedAt: startedAtIso,
|
|
1137
1386
|
endedAt: endedAtIso,
|
|
@@ -1147,7 +1396,21 @@ async function finalizeTraceSession(session, params = {}) {
|
|
|
1147
1396
|
let sinkFailed = false;
|
|
1148
1397
|
const sink = session.resolvedConfig.sink;
|
|
1149
1398
|
const isStreamingMode = session.resolvedConfig.mode === "streaming";
|
|
1150
|
-
if (sink.type === "
|
|
1399
|
+
if (sink.type === "otlp") {
|
|
1400
|
+
try {
|
|
1401
|
+
const otlpPayload = traceSessionToOtlp(payloadForSink);
|
|
1402
|
+
await postOtlpTraces(sink.endpoint, sink.headers, otlpPayload);
|
|
1403
|
+
} catch (err) {
|
|
1404
|
+
sinkFailed = true;
|
|
1405
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1406
|
+
session.errors.push({
|
|
1407
|
+
eventId: "sink",
|
|
1408
|
+
message,
|
|
1409
|
+
type: "sink",
|
|
1410
|
+
timestamp: endedAtIso
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
} else if (sink.type === "cognipeer" || sink.type === "http") {
|
|
1151
1414
|
try {
|
|
1152
1415
|
const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
|
|
1153
1416
|
if (isStreamingMode && session.sessionStarted) {
|
|
@@ -1174,8 +1437,9 @@ async function finalizeTraceSession(session, params = {}) {
|
|
|
1174
1437
|
}
|
|
1175
1438
|
}
|
|
1176
1439
|
const hadNonSinkErrors = session.errors.some((error) => error.type && error.type !== "sink");
|
|
1440
|
+
const hadSinkErrors = session.errors.some((error) => error.type === "sink");
|
|
1177
1441
|
let status = params.status ?? (hadNonSinkErrors ? "error" : "success");
|
|
1178
|
-
if (status === "success" && sinkFailed) {
|
|
1442
|
+
if (status === "success" && (sinkFailed || hadSinkErrors)) {
|
|
1179
1443
|
status = "partial";
|
|
1180
1444
|
}
|
|
1181
1445
|
let finalPayload = buildPayload(status);
|
|
@@ -1279,6 +1543,7 @@ function createAgentCoreNode(opts) {
|
|
|
1279
1543
|
model: opts.model,
|
|
1280
1544
|
tools: opts.tools || [],
|
|
1281
1545
|
guardrails: opts.guardrails,
|
|
1546
|
+
todoListPrompt: opts.todoListPrompt,
|
|
1282
1547
|
limits: opts.limits,
|
|
1283
1548
|
outputSchema: opts.outputSchema,
|
|
1284
1549
|
tracing: opts.tracing
|
|
@@ -1353,7 +1618,7 @@ function createAgentCoreNode(opts) {
|
|
|
1353
1618
|
}
|
|
1354
1619
|
}
|
|
1355
1620
|
}
|
|
1356
|
-
} catch
|
|
1621
|
+
} catch {
|
|
1357
1622
|
}
|
|
1358
1623
|
};
|
|
1359
1624
|
const onEvent = state.ctx?.__onEvent;
|
|
@@ -1469,9 +1734,527 @@ function createAgentCoreNode(opts) {
|
|
|
1469
1734
|
return { messages: messagesWithResponse, usage: state.usage };
|
|
1470
1735
|
};
|
|
1471
1736
|
}
|
|
1737
|
+
|
|
1738
|
+
// src/smart/runtimeConfig.ts
|
|
1739
|
+
var DEFAULT_CRITICAL_TOOLS = ["response", "manage_todo_list", "get_tool_response"];
|
|
1740
|
+
var DEFAULT_PROFILE_CONFIGS = {
|
|
1741
|
+
fast: {
|
|
1742
|
+
limits: { maxToolCalls: 4, maxParallelTools: 1, maxContextTokens: 12e3 },
|
|
1743
|
+
summarization: {
|
|
1744
|
+
enable: true,
|
|
1745
|
+
maxTokens: 12e3,
|
|
1746
|
+
summaryTriggerTokens: 9e3,
|
|
1747
|
+
summaryPromptMaxTokens: 5e3,
|
|
1748
|
+
summaryCompressionRatioTarget: 0.4,
|
|
1749
|
+
summaryMode: "incremental",
|
|
1750
|
+
promptTemplate: "",
|
|
1751
|
+
toolFreeCall: true,
|
|
1752
|
+
integrityCheck: true
|
|
1753
|
+
},
|
|
1754
|
+
context: {
|
|
1755
|
+
policy: "hybrid",
|
|
1756
|
+
lastTurnsToKeep: 6,
|
|
1757
|
+
toolResponsePolicy: "keep_structured",
|
|
1758
|
+
archiveLargeToolResponses: true,
|
|
1759
|
+
retrieveArchivedToolResponseOnDemand: true,
|
|
1760
|
+
budget: {
|
|
1761
|
+
systemReserveTokens: 1e3,
|
|
1762
|
+
goalsReserveTokens: 900,
|
|
1763
|
+
recentTurnsReserveTokens: 3200,
|
|
1764
|
+
toolResponseReserveTokens: 1800
|
|
1765
|
+
}
|
|
1766
|
+
},
|
|
1767
|
+
planning: {
|
|
1768
|
+
mode: "off",
|
|
1769
|
+
replanPolicy: "on_failure",
|
|
1770
|
+
everyNSteps: 0
|
|
1771
|
+
},
|
|
1772
|
+
memory: {
|
|
1773
|
+
provider: "inMemory",
|
|
1774
|
+
scope: "session",
|
|
1775
|
+
writePolicy: "auto_important",
|
|
1776
|
+
readPolicy: "recent_only"
|
|
1777
|
+
},
|
|
1778
|
+
delegation: {
|
|
1779
|
+
mode: "off",
|
|
1780
|
+
maxDelegationDepth: 1,
|
|
1781
|
+
maxChildCalls: 2,
|
|
1782
|
+
maxParallelChild: 1,
|
|
1783
|
+
childContextPolicy: "minimal",
|
|
1784
|
+
requireJsonOutputContract: true
|
|
1785
|
+
},
|
|
1786
|
+
toolResponses: {
|
|
1787
|
+
maxToolResponseChars: 2400,
|
|
1788
|
+
maxToolResponseTokens: 800,
|
|
1789
|
+
defaultPolicy: "keep_structured",
|
|
1790
|
+
largeResponsePolicy: "summarize_archive",
|
|
1791
|
+
toolResponseRetentionByTool: {},
|
|
1792
|
+
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1793
|
+
schemaValidation: "strict",
|
|
1794
|
+
retryOnSchemaError: true,
|
|
1795
|
+
fallbackPolicy: "keep_structured"
|
|
1796
|
+
},
|
|
1797
|
+
watchdog: {
|
|
1798
|
+
enabled: true,
|
|
1799
|
+
tokenDriftThreshold: 1200,
|
|
1800
|
+
overToolingSpikeThreshold: 0.7,
|
|
1801
|
+
contextRotThreshold: 0.65,
|
|
1802
|
+
autoCompaction: true,
|
|
1803
|
+
autoReplanOnFailure: true
|
|
1804
|
+
}
|
|
1805
|
+
},
|
|
1806
|
+
balanced: {
|
|
1807
|
+
limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3 },
|
|
1808
|
+
summarization: {
|
|
1809
|
+
enable: true,
|
|
1810
|
+
maxTokens: 24e3,
|
|
1811
|
+
summaryTriggerTokens: 17e3,
|
|
1812
|
+
summaryPromptMaxTokens: 7e3,
|
|
1813
|
+
summaryCompressionRatioTarget: 0.35,
|
|
1814
|
+
summaryMode: "incremental",
|
|
1815
|
+
promptTemplate: "",
|
|
1816
|
+
toolFreeCall: true,
|
|
1817
|
+
integrityCheck: true
|
|
1818
|
+
},
|
|
1819
|
+
context: {
|
|
1820
|
+
policy: "hybrid",
|
|
1821
|
+
lastTurnsToKeep: 8,
|
|
1822
|
+
toolResponsePolicy: "summarize_archive",
|
|
1823
|
+
archiveLargeToolResponses: true,
|
|
1824
|
+
retrieveArchivedToolResponseOnDemand: true,
|
|
1825
|
+
budget: {
|
|
1826
|
+
systemReserveTokens: 1200,
|
|
1827
|
+
goalsReserveTokens: 1200,
|
|
1828
|
+
recentTurnsReserveTokens: 5200,
|
|
1829
|
+
toolResponseReserveTokens: 2800
|
|
1830
|
+
}
|
|
1831
|
+
},
|
|
1832
|
+
planning: {
|
|
1833
|
+
mode: "off",
|
|
1834
|
+
replanPolicy: "on_failure",
|
|
1835
|
+
everyNSteps: 0
|
|
1836
|
+
},
|
|
1837
|
+
memory: {
|
|
1838
|
+
provider: "inMemory",
|
|
1839
|
+
scope: "session",
|
|
1840
|
+
writePolicy: "auto_important",
|
|
1841
|
+
readPolicy: "hybrid"
|
|
1842
|
+
},
|
|
1843
|
+
delegation: {
|
|
1844
|
+
mode: "role_based",
|
|
1845
|
+
maxDelegationDepth: 2,
|
|
1846
|
+
maxChildCalls: 4,
|
|
1847
|
+
maxParallelChild: 2,
|
|
1848
|
+
childContextPolicy: "scoped",
|
|
1849
|
+
requireJsonOutputContract: true
|
|
1850
|
+
},
|
|
1851
|
+
toolResponses: {
|
|
1852
|
+
maxToolResponseChars: 3600,
|
|
1853
|
+
maxToolResponseTokens: 1200,
|
|
1854
|
+
defaultPolicy: "keep_structured",
|
|
1855
|
+
largeResponsePolicy: "summarize_archive",
|
|
1856
|
+
toolResponseRetentionByTool: {},
|
|
1857
|
+
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1858
|
+
schemaValidation: "strict",
|
|
1859
|
+
retryOnSchemaError: true,
|
|
1860
|
+
fallbackPolicy: "keep_structured"
|
|
1861
|
+
},
|
|
1862
|
+
watchdog: {
|
|
1863
|
+
enabled: true,
|
|
1864
|
+
tokenDriftThreshold: 1800,
|
|
1865
|
+
overToolingSpikeThreshold: 0.6,
|
|
1866
|
+
contextRotThreshold: 0.55,
|
|
1867
|
+
autoCompaction: true,
|
|
1868
|
+
autoReplanOnFailure: true
|
|
1869
|
+
}
|
|
1870
|
+
},
|
|
1871
|
+
deep: {
|
|
1872
|
+
limits: { maxToolCalls: 14, maxParallelTools: 3, maxContextTokens: 42e3 },
|
|
1873
|
+
summarization: {
|
|
1874
|
+
enable: true,
|
|
1875
|
+
maxTokens: 42e3,
|
|
1876
|
+
summaryTriggerTokens: 3e4,
|
|
1877
|
+
summaryPromptMaxTokens: 9e3,
|
|
1878
|
+
summaryCompressionRatioTarget: 0.3,
|
|
1879
|
+
summaryMode: "incremental",
|
|
1880
|
+
promptTemplate: "",
|
|
1881
|
+
toolFreeCall: true,
|
|
1882
|
+
integrityCheck: true
|
|
1883
|
+
},
|
|
1884
|
+
context: {
|
|
1885
|
+
policy: "hybrid",
|
|
1886
|
+
lastTurnsToKeep: 12,
|
|
1887
|
+
toolResponsePolicy: "summarize_archive",
|
|
1888
|
+
archiveLargeToolResponses: true,
|
|
1889
|
+
retrieveArchivedToolResponseOnDemand: true,
|
|
1890
|
+
budget: {
|
|
1891
|
+
systemReserveTokens: 1400,
|
|
1892
|
+
goalsReserveTokens: 1600,
|
|
1893
|
+
recentTurnsReserveTokens: 7e3,
|
|
1894
|
+
toolResponseReserveTokens: 3600
|
|
1895
|
+
}
|
|
1896
|
+
},
|
|
1897
|
+
planning: {
|
|
1898
|
+
mode: "off",
|
|
1899
|
+
replanPolicy: "on_failure",
|
|
1900
|
+
everyNSteps: 0
|
|
1901
|
+
},
|
|
1902
|
+
memory: {
|
|
1903
|
+
provider: "inMemory",
|
|
1904
|
+
scope: "workspace",
|
|
1905
|
+
writePolicy: "auto_important",
|
|
1906
|
+
readPolicy: "hybrid"
|
|
1907
|
+
},
|
|
1908
|
+
delegation: {
|
|
1909
|
+
mode: "role_based",
|
|
1910
|
+
maxDelegationDepth: 3,
|
|
1911
|
+
maxChildCalls: 6,
|
|
1912
|
+
maxParallelChild: 2,
|
|
1913
|
+
childContextPolicy: "scoped",
|
|
1914
|
+
requireJsonOutputContract: true
|
|
1915
|
+
},
|
|
1916
|
+
toolResponses: {
|
|
1917
|
+
maxToolResponseChars: 5400,
|
|
1918
|
+
maxToolResponseTokens: 1800,
|
|
1919
|
+
defaultPolicy: "keep_structured",
|
|
1920
|
+
largeResponsePolicy: "summarize_archive",
|
|
1921
|
+
toolResponseRetentionByTool: {},
|
|
1922
|
+
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1923
|
+
schemaValidation: "strict",
|
|
1924
|
+
retryOnSchemaError: true,
|
|
1925
|
+
fallbackPolicy: "keep_structured"
|
|
1926
|
+
},
|
|
1927
|
+
watchdog: {
|
|
1928
|
+
enabled: true,
|
|
1929
|
+
tokenDriftThreshold: 2200,
|
|
1930
|
+
overToolingSpikeThreshold: 0.55,
|
|
1931
|
+
contextRotThreshold: 0.5,
|
|
1932
|
+
autoCompaction: true,
|
|
1933
|
+
autoReplanOnFailure: true
|
|
1934
|
+
}
|
|
1935
|
+
},
|
|
1936
|
+
research: {
|
|
1937
|
+
limits: { maxToolCalls: 20, maxParallelTools: 4, maxContextTokens: 56e3 },
|
|
1938
|
+
summarization: {
|
|
1939
|
+
enable: true,
|
|
1940
|
+
maxTokens: 56e3,
|
|
1941
|
+
summaryTriggerTokens: 42e3,
|
|
1942
|
+
summaryPromptMaxTokens: 1e4,
|
|
1943
|
+
summaryCompressionRatioTarget: 0.28,
|
|
1944
|
+
summaryMode: "incremental",
|
|
1945
|
+
promptTemplate: "",
|
|
1946
|
+
toolFreeCall: true,
|
|
1947
|
+
integrityCheck: true
|
|
1948
|
+
},
|
|
1949
|
+
context: {
|
|
1950
|
+
policy: "hybrid",
|
|
1951
|
+
lastTurnsToKeep: 20,
|
|
1952
|
+
toolResponsePolicy: "summarize_archive",
|
|
1953
|
+
archiveLargeToolResponses: true,
|
|
1954
|
+
retrieveArchivedToolResponseOnDemand: true,
|
|
1955
|
+
budget: {
|
|
1956
|
+
systemReserveTokens: 1600,
|
|
1957
|
+
goalsReserveTokens: 1800,
|
|
1958
|
+
recentTurnsReserveTokens: 9e3,
|
|
1959
|
+
toolResponseReserveTokens: 4800
|
|
1960
|
+
}
|
|
1961
|
+
},
|
|
1962
|
+
planning: {
|
|
1963
|
+
mode: "off",
|
|
1964
|
+
replanPolicy: "on_failure",
|
|
1965
|
+
everyNSteps: 0
|
|
1966
|
+
},
|
|
1967
|
+
memory: {
|
|
1968
|
+
provider: "inMemory",
|
|
1969
|
+
scope: "workspace",
|
|
1970
|
+
writePolicy: "auto_important",
|
|
1971
|
+
readPolicy: "hybrid"
|
|
1972
|
+
},
|
|
1973
|
+
delegation: {
|
|
1974
|
+
mode: "automatic",
|
|
1975
|
+
maxDelegationDepth: 4,
|
|
1976
|
+
maxChildCalls: 8,
|
|
1977
|
+
maxParallelChild: 3,
|
|
1978
|
+
childContextPolicy: "full",
|
|
1979
|
+
requireJsonOutputContract: true
|
|
1980
|
+
},
|
|
1981
|
+
toolResponses: {
|
|
1982
|
+
maxToolResponseChars: 7200,
|
|
1983
|
+
maxToolResponseTokens: 2200,
|
|
1984
|
+
defaultPolicy: "keep_structured",
|
|
1985
|
+
largeResponsePolicy: "summarize_archive",
|
|
1986
|
+
toolResponseRetentionByTool: {},
|
|
1987
|
+
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1988
|
+
schemaValidation: "strict",
|
|
1989
|
+
retryOnSchemaError: true,
|
|
1990
|
+
fallbackPolicy: "keep_structured"
|
|
1991
|
+
},
|
|
1992
|
+
watchdog: {
|
|
1993
|
+
enabled: true,
|
|
1994
|
+
tokenDriftThreshold: 2600,
|
|
1995
|
+
overToolingSpikeThreshold: 0.5,
|
|
1996
|
+
contextRotThreshold: 0.45,
|
|
1997
|
+
autoCompaction: true,
|
|
1998
|
+
autoReplanOnFailure: true
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
function mergeLimits(base, override) {
|
|
2003
|
+
return {
|
|
2004
|
+
maxToolCalls: override?.maxToolCalls ?? base.maxToolCalls,
|
|
2005
|
+
maxParallelTools: override?.maxParallelTools ?? base.maxParallelTools,
|
|
2006
|
+
maxContextTokens: override?.maxContextTokens ?? base.maxContextTokens
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
function resolveBaseProfile(opts, runtimeProfile) {
|
|
2010
|
+
if (opts.customProfile?.extends) return opts.customProfile.extends;
|
|
2011
|
+
if (runtimeProfile !== "custom") return runtimeProfile;
|
|
2012
|
+
return "balanced";
|
|
2013
|
+
}
|
|
2014
|
+
function normalizeSmartAgentOptions(opts) {
|
|
2015
|
+
const runtimeProfile = opts.runtimeProfile ?? (opts.customProfile ? "custom" : "balanced");
|
|
2016
|
+
const baseProfile = resolveBaseProfile(opts, runtimeProfile);
|
|
2017
|
+
const preset = DEFAULT_PROFILE_CONFIGS[baseProfile];
|
|
2018
|
+
const customProfile = opts.customProfile || {};
|
|
2019
|
+
const useTodoList = opts.useTodoList === true;
|
|
2020
|
+
const summarizationDisabled = opts.summarization === false;
|
|
2021
|
+
const summarizationOverride = typeof opts.summarization === "object" ? opts.summarization : {};
|
|
2022
|
+
const planningMode = opts.planning?.mode ?? customProfile.planning?.mode ?? (useTodoList ? "todo" : preset.planning.mode);
|
|
2023
|
+
const memoryStore = opts.memory?.store ?? customProfile.memory?.store;
|
|
2024
|
+
return {
|
|
2025
|
+
runtimeProfile,
|
|
2026
|
+
baseProfile,
|
|
2027
|
+
limits: mergeLimits(mergeLimits(preset.limits, customProfile.limits), opts.limits),
|
|
2028
|
+
summarization: {
|
|
2029
|
+
...preset.summarization,
|
|
2030
|
+
...customProfile.summarization || {},
|
|
2031
|
+
...summarizationOverride,
|
|
2032
|
+
enable: summarizationDisabled ? false : summarizationOverride.enable ?? customProfile.summarization?.enable ?? preset.summarization.enable,
|
|
2033
|
+
maxTokens: summarizationOverride.maxTokens ?? opts.limits?.maxContextTokens ?? customProfile.summarization?.maxTokens ?? preset.summarization.maxTokens,
|
|
2034
|
+
summaryTriggerTokens: summarizationOverride.summaryTriggerTokens ?? summarizationOverride.maxTokens ?? opts.limits?.maxContextTokens ?? customProfile.summarization?.summaryTriggerTokens ?? customProfile.summarization?.maxTokens ?? preset.summarization.summaryTriggerTokens,
|
|
2035
|
+
promptTemplate: summarizationOverride.promptTemplate ?? customProfile.summarization?.promptTemplate ?? preset.summarization.promptTemplate
|
|
2036
|
+
},
|
|
2037
|
+
context: {
|
|
2038
|
+
...preset.context,
|
|
2039
|
+
...customProfile.context || {},
|
|
2040
|
+
...opts.context || {},
|
|
2041
|
+
budget: {
|
|
2042
|
+
...preset.context.budget,
|
|
2043
|
+
...customProfile.context?.budget || {},
|
|
2044
|
+
...opts.context?.budget || {}
|
|
2045
|
+
},
|
|
2046
|
+
toolResponsePolicy: opts.context?.toolResponsePolicy ?? customProfile.context?.toolResponsePolicy ?? preset.context.toolResponsePolicy
|
|
2047
|
+
},
|
|
2048
|
+
planning: {
|
|
2049
|
+
...preset.planning,
|
|
2050
|
+
...customProfile.planning || {},
|
|
2051
|
+
...opts.planning || {},
|
|
2052
|
+
mode: planningMode
|
|
2053
|
+
},
|
|
2054
|
+
memory: {
|
|
2055
|
+
...preset.memory,
|
|
2056
|
+
...customProfile.memory || {},
|
|
2057
|
+
...opts.memory || {},
|
|
2058
|
+
store: memoryStore
|
|
2059
|
+
},
|
|
2060
|
+
delegation: {
|
|
2061
|
+
...preset.delegation,
|
|
2062
|
+
...customProfile.delegation || {},
|
|
2063
|
+
...opts.delegation || {}
|
|
2064
|
+
},
|
|
2065
|
+
toolResponses: {
|
|
2066
|
+
...preset.toolResponses,
|
|
2067
|
+
...customProfile.toolResponses || {},
|
|
2068
|
+
...opts.toolResponses || {},
|
|
2069
|
+
toolResponseRetentionByTool: {
|
|
2070
|
+
...preset.toolResponses.toolResponseRetentionByTool,
|
|
2071
|
+
...customProfile.toolResponses?.toolResponseRetentionByTool || {},
|
|
2072
|
+
...opts.toolResponses?.toolResponseRetentionByTool || {}
|
|
2073
|
+
},
|
|
2074
|
+
criticalTools: opts.toolResponses?.criticalTools ?? customProfile.toolResponses?.criticalTools ?? preset.toolResponses.criticalTools
|
|
2075
|
+
},
|
|
2076
|
+
watchdog: {
|
|
2077
|
+
...preset.watchdog,
|
|
2078
|
+
...customProfile.watchdog || {},
|
|
2079
|
+
...opts.watchdog || {}
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
function getResolvedSmartConfig(opts, runtime) {
|
|
2084
|
+
return runtime?.smart || normalizeSmartAgentOptions(opts);
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
// src/utils/utilTokens.ts
|
|
2088
|
+
function countApproxTokens(text) {
|
|
2089
|
+
if (!text) return 0;
|
|
2090
|
+
const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
|
|
2091
|
+
const asciiCount = text.length - nonAsciiCount;
|
|
2092
|
+
return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
|
|
2093
|
+
}
|
|
2094
|
+
function extractMessageText(m) {
|
|
2095
|
+
if (!m) return "";
|
|
2096
|
+
const parts = [];
|
|
2097
|
+
parts.push(m.role || "");
|
|
2098
|
+
if (m.name) parts.push(m.name);
|
|
2099
|
+
if (typeof m.content === "string") {
|
|
2100
|
+
parts.push(m.content);
|
|
2101
|
+
} else if (Array.isArray(m.content)) {
|
|
2102
|
+
for (const c of m.content) {
|
|
2103
|
+
if (typeof c === "string") parts.push(c);
|
|
2104
|
+
else if (c?.text) parts.push(c.text);
|
|
2105
|
+
else if (c?.content) parts.push(c.content);
|
|
2106
|
+
else if (c?.type === "tool_use" && c?.input) {
|
|
2107
|
+
parts.push(c.name || "");
|
|
2108
|
+
parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
|
|
2109
|
+
} else if (c != null && typeof c === "object") {
|
|
2110
|
+
parts.push(JSON.stringify(c));
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
} else if (m.content != null && typeof m.content === "object") {
|
|
2114
|
+
parts.push(JSON.stringify(m.content));
|
|
2115
|
+
}
|
|
2116
|
+
if (Array.isArray(m.tool_calls)) {
|
|
2117
|
+
for (const tc of m.tool_calls) {
|
|
2118
|
+
if (tc?.function?.name) parts.push(tc.function.name);
|
|
2119
|
+
if (tc?.function?.arguments) {
|
|
2120
|
+
parts.push(
|
|
2121
|
+
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
if (tc?.name && !tc?.function?.name) parts.push(tc.name);
|
|
2125
|
+
if (tc?.args && !tc?.function?.arguments) {
|
|
2126
|
+
parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
if (Array.isArray(m.additional_kwargs?.tool_calls)) {
|
|
2131
|
+
for (const tc of m.additional_kwargs.tool_calls) {
|
|
2132
|
+
if (tc?.function?.name) parts.push(tc.function.name);
|
|
2133
|
+
if (tc?.function?.arguments) {
|
|
2134
|
+
parts.push(
|
|
2135
|
+
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2136
|
+
);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
return parts.join("\n");
|
|
2141
|
+
}
|
|
2142
|
+
function countMessagesTokens(messages) {
|
|
2143
|
+
if (!Array.isArray(messages) || messages.length === 0) return 0;
|
|
2144
|
+
const allText = messages.map(extractMessageText).join("\n");
|
|
2145
|
+
return countApproxTokens(allText) + messages.length * 4;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// src/smart/toolResponses.ts
|
|
2149
|
+
function safeStringify(value) {
|
|
2150
|
+
if (typeof value === "string") return value;
|
|
2151
|
+
try {
|
|
2152
|
+
const serialized = JSON.stringify(value);
|
|
2153
|
+
if (typeof serialized === "string") {
|
|
2154
|
+
return serialized;
|
|
2155
|
+
}
|
|
2156
|
+
} catch {
|
|
2157
|
+
}
|
|
2158
|
+
return String(value);
|
|
2159
|
+
}
|
|
2160
|
+
function summarizeObject(value) {
|
|
2161
|
+
if (value == null) return "null";
|
|
2162
|
+
if (typeof value === "string") {
|
|
2163
|
+
return value.length > 240 ? `${value.slice(0, 240)}...` : value;
|
|
2164
|
+
}
|
|
2165
|
+
if (Array.isArray(value)) {
|
|
2166
|
+
const preview = value.slice(0, 3).map((entry) => safeStringify(entry).slice(0, 120)).join(" | ");
|
|
2167
|
+
return `array(length=${value.length}) ${preview}`.trim();
|
|
2168
|
+
}
|
|
2169
|
+
if (typeof value === "object") {
|
|
2170
|
+
const keys = Object.keys(value);
|
|
2171
|
+
const preview = keys.slice(0, 6).map((key) => `${key}=${safeStringify(value[key]).slice(0, 80)}`).join("; ");
|
|
2172
|
+
return `object(keys=${keys.length}) ${preview}`.trim();
|
|
2173
|
+
}
|
|
2174
|
+
return safeStringify(value);
|
|
2175
|
+
}
|
|
2176
|
+
function classifyToolResponse(toolName, output, config) {
|
|
2177
|
+
const serialized = safeStringify(output);
|
|
2178
|
+
const tokenCount = countApproxTokens(serialized);
|
|
2179
|
+
const criticalTools = new Set(config.toolResponses.criticalTools);
|
|
2180
|
+
if (criticalTools.has(toolName)) return "critical";
|
|
2181
|
+
if (tokenCount >= config.toolResponses.maxToolResponseTokens || serialized.length >= config.toolResponses.maxToolResponseChars) {
|
|
2182
|
+
return "verbose";
|
|
2183
|
+
}
|
|
2184
|
+
if (serialized === "ok" || serialized === "true" || serialized === "null") {
|
|
2185
|
+
return "redundant";
|
|
2186
|
+
}
|
|
2187
|
+
return "informative";
|
|
2188
|
+
}
|
|
2189
|
+
function resolveToolResponsePolicy(toolName, output, config) {
|
|
2190
|
+
const serialized = safeStringify(output);
|
|
2191
|
+
const tokenCount = countApproxTokens(serialized);
|
|
2192
|
+
const classification = classifyToolResponse(toolName, output, config);
|
|
2193
|
+
const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
|
|
2194
|
+
let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
|
|
2195
|
+
if (classification === "critical") {
|
|
2196
|
+
retentionPolicy = byTool || "keep_structured";
|
|
2197
|
+
} else if (classification === "verbose") {
|
|
2198
|
+
retentionPolicy = byTool || config.toolResponses.largeResponsePolicy;
|
|
2199
|
+
} else if (classification === "redundant") {
|
|
2200
|
+
retentionPolicy = byTool || "drop";
|
|
2201
|
+
}
|
|
2202
|
+
if (retentionPolicy === "keep_full") {
|
|
2203
|
+
const summary2 = "";
|
|
2204
|
+
return { classification, retentionPolicy, content: serialized, rawOutput: output, summary: summary2, tokenCount };
|
|
2205
|
+
}
|
|
2206
|
+
const summary = summarizeObject(output);
|
|
2207
|
+
if (retentionPolicy === "keep_structured") {
|
|
2208
|
+
return { classification, retentionPolicy, content: summary, rawOutput: output, summary, tokenCount };
|
|
2209
|
+
}
|
|
2210
|
+
if (retentionPolicy === "summarize_archive") {
|
|
2211
|
+
return {
|
|
2212
|
+
classification,
|
|
2213
|
+
retentionPolicy,
|
|
2214
|
+
content: `ARCHIVED_TOOL_RESPONSE: ${summary}`,
|
|
2215
|
+
rawOutput: output,
|
|
2216
|
+
summary,
|
|
2217
|
+
tokenCount
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
return {
|
|
2221
|
+
classification,
|
|
2222
|
+
retentionPolicy,
|
|
2223
|
+
content: `DROPPED_TOOL_RESPONSE: ${summary}`,
|
|
2224
|
+
rawOutput: output,
|
|
2225
|
+
summary,
|
|
2226
|
+
tokenCount
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
function validateToolArgs(tool, args) {
|
|
2230
|
+
const schema = tool.schema;
|
|
2231
|
+
if (!schema || typeof schema.parse !== "function") {
|
|
2232
|
+
return { ok: true, value: args };
|
|
2233
|
+
}
|
|
2234
|
+
try {
|
|
2235
|
+
return { ok: true, value: schema.parse(args) };
|
|
2236
|
+
} catch (error) {
|
|
2237
|
+
if (error instanceof zod.ZodError) {
|
|
2238
|
+
const message = error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
|
|
2239
|
+
return { ok: false, message };
|
|
2240
|
+
}
|
|
2241
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// src/nodes/tools.ts
|
|
2246
|
+
function normalizeMaxExecutionsPerRun(value) {
|
|
2247
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
2248
|
+
const parsed = Number(value);
|
|
2249
|
+
if (!Number.isFinite(parsed)) return null;
|
|
2250
|
+
const normalized = Math.trunc(parsed);
|
|
2251
|
+
return normalized >= 0 ? normalized : null;
|
|
2252
|
+
}
|
|
2253
|
+
function countSuccessfulToolExecutions(toolHistory, toolName) {
|
|
2254
|
+
if (!Array.isArray(toolHistory)) return 0;
|
|
2255
|
+
return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
|
|
2256
|
+
}
|
|
1472
2257
|
function createToolsNode(initialTools, opts) {
|
|
1473
|
-
const baseToolByName = /* @__PURE__ */ new Map();
|
|
1474
|
-
for (const t of initialTools) baseToolByName.set(t.name, t);
|
|
1475
2258
|
return async (state) => {
|
|
1476
2259
|
const runtime = state.agent || {
|
|
1477
2260
|
name: opts?.name,
|
|
@@ -1479,15 +2262,17 @@ function createToolsNode(initialTools, opts) {
|
|
|
1479
2262
|
tools: initialTools,
|
|
1480
2263
|
limits: opts?.limits,
|
|
1481
2264
|
systemPrompt: opts?.systemPrompt,
|
|
2265
|
+
todoListPrompt: opts?.todoListPrompt,
|
|
1482
2266
|
useTodoList: opts?.useTodoList,
|
|
1483
2267
|
outputSchema: opts?.outputSchema
|
|
1484
2268
|
};
|
|
2269
|
+
const resolved = getResolvedSmartConfig(opts || {}, runtime);
|
|
1485
2270
|
const activeTools = runtime.tools;
|
|
1486
2271
|
const toolByName = /* @__PURE__ */ new Map();
|
|
1487
|
-
for (const
|
|
2272
|
+
for (const tool of activeTools) toolByName.set(tool.name, tool);
|
|
1488
2273
|
const limits = {
|
|
1489
|
-
maxToolCalls: runtime.limits?.maxToolCalls ?? 10,
|
|
1490
|
-
maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? 1)
|
|
2274
|
+
maxToolCalls: runtime.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10,
|
|
2275
|
+
maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? resolved.limits.maxParallelTools ?? 1)
|
|
1491
2276
|
};
|
|
1492
2277
|
const appended = [];
|
|
1493
2278
|
const onEvent = state.ctx?.__onEvent;
|
|
@@ -1498,20 +2283,19 @@ function createToolsNode(initialTools, opts) {
|
|
|
1498
2283
|
const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
|
|
1499
2284
|
const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
|
|
1500
2285
|
let awaitingApproval = false;
|
|
1501
|
-
for (const
|
|
1502
|
-
const
|
|
1503
|
-
if (
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
2286
|
+
for (const tool of toolByName.values()) {
|
|
2287
|
+
const anyTool = tool;
|
|
2288
|
+
if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
|
|
2289
|
+
anyTool._stateRef.toolHistory = state.toolHistory;
|
|
2290
|
+
anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
|
|
2291
|
+
anyTool._stateRef.__onEvent = onEvent;
|
|
1507
2292
|
}
|
|
1508
2293
|
}
|
|
1509
2294
|
const last = state.messages[state.messages.length - 1];
|
|
1510
2295
|
let toolCount = state.toolCallCount || 0;
|
|
1511
|
-
const toolCalls = Array.isArray(
|
|
1512
|
-
last?.tool_calls
|
|
1513
|
-
) ? last.tool_calls : [];
|
|
2296
|
+
const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
|
|
1514
2297
|
const toolHistory = state.toolHistory || [];
|
|
2298
|
+
const toolHistoryArchived = state.toolHistoryArchived || [];
|
|
1515
2299
|
const remaining = Math.max(0, limits.maxToolCalls - toolCount);
|
|
1516
2300
|
const planned = toolCalls.slice(0, remaining);
|
|
1517
2301
|
const skipped = toolCalls.slice(remaining);
|
|
@@ -1520,6 +2304,17 @@ function createToolsNode(initialTools, opts) {
|
|
|
1520
2304
|
if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
|
|
1521
2305
|
return { cancelled: false, reason: void 0 };
|
|
1522
2306
|
};
|
|
2307
|
+
const markToolFailure = (message, toolName, toolCallId) => {
|
|
2308
|
+
const ctx = { ...state.ctx || {} };
|
|
2309
|
+
ctx.__lastToolError = message;
|
|
2310
|
+
if (toolCallId) {
|
|
2311
|
+
ctx.__toolSchemaError = { toolName, toolCallId, message };
|
|
2312
|
+
}
|
|
2313
|
+
if (resolved.watchdog.autoReplanOnFailure) {
|
|
2314
|
+
ctx.__planNeedsReplan = true;
|
|
2315
|
+
}
|
|
2316
|
+
state.ctx = ctx;
|
|
2317
|
+
};
|
|
1523
2318
|
const runOne = async (tc) => {
|
|
1524
2319
|
const cancelState = isCancelled();
|
|
1525
2320
|
if (cancelState.cancelled) {
|
|
@@ -1529,15 +2324,10 @@ function createToolsNode(initialTools, opts) {
|
|
|
1529
2324
|
onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
|
|
1530
2325
|
return { status: "error" };
|
|
1531
2326
|
}
|
|
1532
|
-
const
|
|
1533
|
-
if (!
|
|
2327
|
+
const tool = toolByName.get(tc.name);
|
|
2328
|
+
if (!tool) {
|
|
2329
|
+
appended.push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1534
2330
|
onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
|
|
1535
|
-
appended.push({
|
|
1536
|
-
role: "tool",
|
|
1537
|
-
content: `Tool not found: ${tc.name}`,
|
|
1538
|
-
tool_call_id: tc.id || `${tc.name}_${appended.length}`,
|
|
1539
|
-
name: tc.name
|
|
1540
|
-
});
|
|
1541
2331
|
toolCount += 1;
|
|
1542
2332
|
return { status: "error" };
|
|
1543
2333
|
}
|
|
@@ -1545,22 +2335,67 @@ function createToolsNode(initialTools, opts) {
|
|
|
1545
2335
|
if (typeof args === "string") {
|
|
1546
2336
|
try {
|
|
1547
2337
|
args = JSON.parse(args);
|
|
1548
|
-
} catch
|
|
2338
|
+
} catch {
|
|
1549
2339
|
}
|
|
1550
2340
|
}
|
|
1551
2341
|
const toolCallId = tc.id || `${tc.name}_${toolCount + 1}`;
|
|
2342
|
+
const validatedArgs = validateToolArgs(tool, args);
|
|
2343
|
+
if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
|
|
2344
|
+
const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
|
|
2345
|
+
appended.push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
|
|
2346
|
+
markToolFailure(errorMessage, tc.name, toolCallId);
|
|
2347
|
+
onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
|
|
2348
|
+
toolCount += 1;
|
|
2349
|
+
return { status: "error" };
|
|
2350
|
+
}
|
|
2351
|
+
if (validatedArgs.ok) {
|
|
2352
|
+
args = validatedArgs.value;
|
|
2353
|
+
}
|
|
2354
|
+
const toolName = tool.name || tc.name;
|
|
2355
|
+
const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
|
|
2356
|
+
if (maxExecutionsPerRun !== null) {
|
|
2357
|
+
const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
|
|
2358
|
+
if (currentExecutions >= maxExecutionsPerRun) {
|
|
2359
|
+
const limitMessage = `Skipped tool due to per-tool execution limit: ${toolName} (${maxExecutionsPerRun}/run)`;
|
|
2360
|
+
appended.push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
|
|
2361
|
+
onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args });
|
|
2362
|
+
recordTraceEvent(traceSession, {
|
|
2363
|
+
type: "tool_call",
|
|
2364
|
+
label: `Tool Skipped - ${toolName}`,
|
|
2365
|
+
actor: { scope: "tool", name: toolName, role: "tool" },
|
|
2366
|
+
status: "skipped",
|
|
2367
|
+
toolExecutionId: tc.id,
|
|
2368
|
+
messageList: [
|
|
2369
|
+
{
|
|
2370
|
+
role: "assistant",
|
|
2371
|
+
name: toolName,
|
|
2372
|
+
content: limitMessage,
|
|
2373
|
+
tool_calls: [
|
|
2374
|
+
{
|
|
2375
|
+
id: tc.id,
|
|
2376
|
+
type: "function",
|
|
2377
|
+
function: { name: toolName, arguments: sanitizeTracePayload(args) }
|
|
2378
|
+
}
|
|
2379
|
+
]
|
|
2380
|
+
}
|
|
2381
|
+
]
|
|
2382
|
+
});
|
|
2383
|
+
toolCount += 1;
|
|
2384
|
+
return { status: "error" };
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
1552
2387
|
let approvalEntry = pendingByCallId.get(toolCallId);
|
|
1553
|
-
const needsApproval = Boolean(
|
|
2388
|
+
const needsApproval = Boolean(tool.needsApproval);
|
|
1554
2389
|
if (needsApproval) {
|
|
1555
2390
|
if (!approvalEntry) {
|
|
1556
2391
|
approvalEntry = {
|
|
1557
2392
|
id: nanoid.nanoid(),
|
|
1558
2393
|
toolCallId,
|
|
1559
|
-
toolName:
|
|
2394
|
+
toolName: tool.name || tc.name,
|
|
1560
2395
|
args,
|
|
1561
2396
|
status: "pending",
|
|
1562
2397
|
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1563
|
-
metadata:
|
|
2398
|
+
metadata: tool.approvalPrompt || tool.approvalDefaults ? { prompt: tool.approvalPrompt, defaults: tool.approvalDefaults } : void 0
|
|
1564
2399
|
};
|
|
1565
2400
|
pendingApprovals.push(approvalEntry);
|
|
1566
2401
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
@@ -1582,71 +2417,97 @@ function createToolsNode(initialTools, opts) {
|
|
|
1582
2417
|
}
|
|
1583
2418
|
if (approvalEntry.status === "rejected") {
|
|
1584
2419
|
const rejectionMessage = approvalEntry.comment || "Tool call rejected by reviewer.";
|
|
1585
|
-
appended.push({
|
|
1586
|
-
role: "tool",
|
|
1587
|
-
content: `Tool call rejected: ${rejectionMessage}`,
|
|
1588
|
-
tool_call_id: toolCallId,
|
|
1589
|
-
name: tc.name
|
|
1590
|
-
});
|
|
1591
|
-
onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
|
|
2420
|
+
appended.push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
|
|
1592
2421
|
approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
|
|
1593
2422
|
approvalEntry.status = "executed";
|
|
1594
2423
|
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1595
|
-
toolHistory.push({ executionId: nanoid.nanoid(), toolName: t.name, args, output: `Rejected: ${rejectionMessage}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
|
|
1596
2424
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
2425
|
+
toolHistory.push({ executionId: nanoid.nanoid(), toolName: tool.name, args, output: `Rejected: ${rejectionMessage}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "rejected" });
|
|
2426
|
+
onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
|
|
1597
2427
|
toolCount += 1;
|
|
1598
2428
|
return { status: "rejected", approval: approvalEntry };
|
|
1599
2429
|
}
|
|
1600
|
-
if (approvalEntry.status === "approved") {
|
|
1601
|
-
|
|
1602
|
-
args = approvalEntry.approvedArgs;
|
|
1603
|
-
}
|
|
2430
|
+
if (approvalEntry.status === "approved" && approvalEntry.approvedArgs !== void 0) {
|
|
2431
|
+
args = approvalEntry.approvedArgs;
|
|
1604
2432
|
onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
|
|
1605
2433
|
}
|
|
1606
2434
|
}
|
|
1607
2435
|
const start = Date.now();
|
|
2436
|
+
const sanitizedArgs = sanitizeTracePayload(args);
|
|
2437
|
+
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1608
2438
|
try {
|
|
1609
|
-
onEvent?.({ type: "tool_call", phase: "start", name:
|
|
2439
|
+
onEvent?.({ type: "tool_call", phase: "start", name: toolName, id: tc.id, args });
|
|
1610
2440
|
onProgress?.({ stage: "tools", message: `Running tool ${tc.name}` });
|
|
1611
|
-
const
|
|
1612
|
-
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1613
|
-
let output;
|
|
1614
|
-
const anyTool = t;
|
|
2441
|
+
const anyTool = tool;
|
|
1615
2442
|
const callOptions = { cancellationToken, signal: abortSignal };
|
|
2443
|
+
let output;
|
|
1616
2444
|
if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
|
|
1617
2445
|
else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
|
|
1618
2446
|
else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
|
|
1619
2447
|
else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
|
|
1620
2448
|
else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
|
|
1621
2449
|
else throw new Error("Tool is not invokable");
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
toolHistory.push({ executionId
|
|
2450
|
+
const durationMs = Date.now() - start;
|
|
2451
|
+
const executionId = nanoid.nanoid();
|
|
2452
|
+
if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
|
|
2453
|
+
toolHistory.push({ executionId, toolName, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "handoff" });
|
|
1626
2454
|
appended.push({ role: "tool", content: "ok", tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1627
|
-
|
|
1628
|
-
state.agent
|
|
1629
|
-
onEvent?.({ type: "tool_call", phase: "success", name:
|
|
1630
|
-
toolCount += 1;
|
|
2455
|
+
state.agent = output.__handoff.runtime;
|
|
2456
|
+
onEvent?.({ type: "handoff", from: runtime.name, to: state.agent?.name, toolName });
|
|
2457
|
+
onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: "handoff", durationMs });
|
|
1631
2458
|
if (needsApproval && approvalEntry) {
|
|
1632
2459
|
approvalEntry.status = "executed";
|
|
1633
2460
|
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1634
|
-
approvalEntry.executionId =
|
|
2461
|
+
approvalEntry.executionId = executionId;
|
|
1635
2462
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
1636
2463
|
}
|
|
2464
|
+
toolCount += 1;
|
|
1637
2465
|
return { status: "success", approval: approvalEntry };
|
|
1638
2466
|
}
|
|
1639
|
-
const content = typeof output === "string" ? output : JSON.stringify(output);
|
|
1640
2467
|
if (output && typeof output === "object" && output.__finalStructuredOutput) {
|
|
1641
2468
|
if (!state.ctx) state.ctx = {};
|
|
1642
2469
|
state.ctx.__structuredOutputParsed = output.data;
|
|
1643
2470
|
state.ctx.__finalizedDueToStructuredOutput = true;
|
|
1644
2471
|
}
|
|
1645
|
-
const
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
2472
|
+
const responsePolicy = resolveToolResponsePolicy(toolName, output, resolved);
|
|
2473
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2474
|
+
const storedOutput = responsePolicy.retentionPolicy === "keep_full" ? output : responsePolicy.summary;
|
|
2475
|
+
toolHistory.push({
|
|
2476
|
+
executionId,
|
|
2477
|
+
toolName,
|
|
2478
|
+
args,
|
|
2479
|
+
output: storedOutput,
|
|
2480
|
+
rawOutput: output,
|
|
2481
|
+
timestamp,
|
|
2482
|
+
tool_call_id: tc.id,
|
|
2483
|
+
summarized: responsePolicy.retentionPolicy !== "keep_full",
|
|
2484
|
+
originalTokenCount: responsePolicy.tokenCount,
|
|
2485
|
+
classification: responsePolicy.classification,
|
|
2486
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2487
|
+
summary: responsePolicy.summary,
|
|
2488
|
+
archiveId: responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop" ? executionId : void 0,
|
|
2489
|
+
status: "success"
|
|
2490
|
+
});
|
|
2491
|
+
if (resolved.context.archiveLargeToolResponses && (responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop")) {
|
|
2492
|
+
toolHistoryArchived.push({
|
|
2493
|
+
executionId,
|
|
2494
|
+
toolName,
|
|
2495
|
+
args,
|
|
2496
|
+
output,
|
|
2497
|
+
rawOutput: output,
|
|
2498
|
+
timestamp,
|
|
2499
|
+
tool_call_id: tc.id,
|
|
2500
|
+
summarized: true,
|
|
2501
|
+
originalTokenCount: responsePolicy.tokenCount,
|
|
2502
|
+
classification: responsePolicy.classification,
|
|
2503
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2504
|
+
summary: responsePolicy.summary,
|
|
2505
|
+
archiveId: executionId,
|
|
2506
|
+
status: "success"
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
2510
|
+
onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
|
|
1650
2511
|
onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
|
|
1651
2512
|
if (needsApproval && approvalEntry) {
|
|
1652
2513
|
approvalEntry.status = "executed";
|
|
@@ -1656,7 +2517,6 @@ function createToolsNode(initialTools, opts) {
|
|
|
1656
2517
|
}
|
|
1657
2518
|
const sanitizedOutput = sanitizeTracePayload(output);
|
|
1658
2519
|
const outputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedOutput) : void 0;
|
|
1659
|
-
const toolName = t.name || tc.name;
|
|
1660
2520
|
const messageList = [
|
|
1661
2521
|
{
|
|
1662
2522
|
role: "assistant",
|
|
@@ -1676,7 +2536,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1676
2536
|
{
|
|
1677
2537
|
role: "tool",
|
|
1678
2538
|
name: toolName,
|
|
1679
|
-
content:
|
|
2539
|
+
content: responsePolicy.content
|
|
1680
2540
|
}
|
|
1681
2541
|
];
|
|
1682
2542
|
recordTraceEvent(traceSession, {
|
|
@@ -1687,43 +2547,24 @@ function createToolsNode(initialTools, opts) {
|
|
|
1687
2547
|
requestBytes: inputBytes,
|
|
1688
2548
|
responseBytes: outputBytes,
|
|
1689
2549
|
toolExecutionId: executionId,
|
|
1690
|
-
messageList
|
|
2550
|
+
messageList,
|
|
2551
|
+
debug: {
|
|
2552
|
+
classification: responsePolicy.classification,
|
|
2553
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2554
|
+
originalTokenCount: responsePolicy.tokenCount
|
|
2555
|
+
}
|
|
1691
2556
|
});
|
|
1692
2557
|
toolCount += 1;
|
|
1693
2558
|
return { status: "success", approval: approvalEntry };
|
|
1694
|
-
} catch (
|
|
2559
|
+
} catch (error) {
|
|
1695
2560
|
const durationMs = Date.now() - start;
|
|
1696
2561
|
const executionId = nanoid.nanoid();
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
const toolName = t.name || tc.name;
|
|
1704
|
-
const errorMessage = e?.message || String(e);
|
|
1705
|
-
const messageList = [
|
|
1706
|
-
{
|
|
1707
|
-
role: "assistant",
|
|
1708
|
-
name: toolName,
|
|
1709
|
-
content: "",
|
|
1710
|
-
tool_calls: [
|
|
1711
|
-
{
|
|
1712
|
-
id: tc.id || executionId,
|
|
1713
|
-
type: "function",
|
|
1714
|
-
function: {
|
|
1715
|
-
name: toolName,
|
|
1716
|
-
arguments: sanitizedArgs
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
]
|
|
1720
|
-
},
|
|
1721
|
-
{
|
|
1722
|
-
role: "tool",
|
|
1723
|
-
name: toolName,
|
|
1724
|
-
content: `Error executing tool: ${errorMessage}`
|
|
1725
|
-
}
|
|
1726
|
-
];
|
|
2562
|
+
const message = error?.message || String(error);
|
|
2563
|
+
toolHistory.push({ executionId, toolName, args, output: `Error executing tool: ${message}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "error" });
|
|
2564
|
+
appended.push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
2565
|
+
markToolFailure(message, toolName, toolCallId);
|
|
2566
|
+
onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
|
|
2567
|
+
onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
|
|
1727
2568
|
recordTraceEvent(traceSession, {
|
|
1728
2569
|
type: "tool_call",
|
|
1729
2570
|
label: `Tool Error - ${toolName}`,
|
|
@@ -1732,8 +2573,26 @@ function createToolsNode(initialTools, opts) {
|
|
|
1732
2573
|
durationMs,
|
|
1733
2574
|
requestBytes: inputBytes,
|
|
1734
2575
|
toolExecutionId: executionId,
|
|
1735
|
-
error: { message
|
|
1736
|
-
messageList
|
|
2576
|
+
error: { message, stack: error?.stack },
|
|
2577
|
+
messageList: [
|
|
2578
|
+
{
|
|
2579
|
+
role: "assistant",
|
|
2580
|
+
name: toolName,
|
|
2581
|
+
content: "",
|
|
2582
|
+
tool_calls: [
|
|
2583
|
+
{
|
|
2584
|
+
id: tc.id || executionId,
|
|
2585
|
+
type: "function",
|
|
2586
|
+
function: { name: toolName, arguments: sanitizedArgs }
|
|
2587
|
+
}
|
|
2588
|
+
]
|
|
2589
|
+
},
|
|
2590
|
+
{
|
|
2591
|
+
role: "tool",
|
|
2592
|
+
name: toolName,
|
|
2593
|
+
content: `Error executing tool: ${message}`
|
|
2594
|
+
}
|
|
2595
|
+
]
|
|
1737
2596
|
});
|
|
1738
2597
|
toolCount += 1;
|
|
1739
2598
|
return { status: "error", approval: approvalEntry };
|
|
@@ -1750,7 +2609,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1750
2609
|
break;
|
|
1751
2610
|
}
|
|
1752
2611
|
const result = await runOne(tc);
|
|
1753
|
-
if (result
|
|
2612
|
+
if (result.status === "awaiting_approval") {
|
|
1754
2613
|
awaitingApproval = true;
|
|
1755
2614
|
break;
|
|
1756
2615
|
}
|
|
@@ -1758,37 +2617,28 @@ function createToolsNode(initialTools, opts) {
|
|
|
1758
2617
|
for (const tc of skipped) {
|
|
1759
2618
|
onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
|
|
1760
2619
|
const sanitizedArgs = sanitizeTracePayload(tc.args);
|
|
1761
|
-
const messageList = [
|
|
1762
|
-
{
|
|
1763
|
-
role: "assistant",
|
|
1764
|
-
name: tc.name,
|
|
1765
|
-
content: `Skipped tool due to tool-call limit: ${tc.name}`,
|
|
1766
|
-
tool_calls: [
|
|
1767
|
-
{
|
|
1768
|
-
id: tc.id,
|
|
1769
|
-
type: "function",
|
|
1770
|
-
function: {
|
|
1771
|
-
name: tc.name,
|
|
1772
|
-
arguments: sanitizedArgs
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
]
|
|
1776
|
-
}
|
|
1777
|
-
];
|
|
1778
2620
|
recordTraceEvent(traceSession, {
|
|
1779
2621
|
type: "tool_call",
|
|
1780
2622
|
label: `Tool Skipped - ${tc.name}`,
|
|
1781
2623
|
actor: { scope: "tool", name: tc.name, role: "tool" },
|
|
1782
2624
|
status: "skipped",
|
|
1783
2625
|
toolExecutionId: tc.id,
|
|
1784
|
-
messageList
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
2626
|
+
messageList: [
|
|
2627
|
+
{
|
|
2628
|
+
role: "assistant",
|
|
2629
|
+
name: tc.name,
|
|
2630
|
+
content: `Skipped tool due to tool-call limit: ${tc.name}`,
|
|
2631
|
+
tool_calls: [
|
|
2632
|
+
{
|
|
2633
|
+
id: tc.id,
|
|
2634
|
+
type: "function",
|
|
2635
|
+
function: { name: tc.name, arguments: sanitizedArgs }
|
|
2636
|
+
}
|
|
2637
|
+
]
|
|
2638
|
+
}
|
|
2639
|
+
]
|
|
1791
2640
|
});
|
|
2641
|
+
appended.push({ role: "tool", content: `Skipped tool due to tool-call limit: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1792
2642
|
toolCount += 1;
|
|
1793
2643
|
}
|
|
1794
2644
|
if (!awaitingApproval && state.ctx?.__awaitingApproval) {
|
|
@@ -1803,6 +2653,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1803
2653
|
messages: [...state.messages, ...appended],
|
|
1804
2654
|
toolCallCount: toolCount,
|
|
1805
2655
|
toolHistory,
|
|
2656
|
+
toolHistoryArchived,
|
|
1806
2657
|
agent: state.agent,
|
|
1807
2658
|
pendingApprovals
|
|
1808
2659
|
};
|
|
@@ -2027,68 +2878,20 @@ function resolveToolApprovalState(state, resolution) {
|
|
|
2027
2878
|
};
|
|
2028
2879
|
}
|
|
2029
2880
|
|
|
2030
|
-
// src/
|
|
2031
|
-
function
|
|
2032
|
-
if (!
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
|
|
2036
|
-
}
|
|
2037
|
-
function extractMessageText(m) {
|
|
2038
|
-
if (!m) return "";
|
|
2039
|
-
const parts = [];
|
|
2040
|
-
parts.push(m.role || "");
|
|
2041
|
-
if (m.name) parts.push(m.name);
|
|
2042
|
-
if (typeof m.content === "string") {
|
|
2043
|
-
parts.push(m.content);
|
|
2044
|
-
} else if (Array.isArray(m.content)) {
|
|
2045
|
-
for (const c of m.content) {
|
|
2046
|
-
if (typeof c === "string") parts.push(c);
|
|
2047
|
-
else if (c?.text) parts.push(c.text);
|
|
2048
|
-
else if (c?.content) parts.push(c.content);
|
|
2049
|
-
else if (c?.type === "tool_use" && c?.input) {
|
|
2050
|
-
parts.push(c.name || "");
|
|
2051
|
-
parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
|
|
2052
|
-
} else if (c != null && typeof c === "object") {
|
|
2053
|
-
parts.push(JSON.stringify(c));
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
} else if (m.content != null && typeof m.content === "object") {
|
|
2057
|
-
parts.push(JSON.stringify(m.content));
|
|
2058
|
-
}
|
|
2059
|
-
if (Array.isArray(m.tool_calls)) {
|
|
2060
|
-
for (const tc of m.tool_calls) {
|
|
2061
|
-
if (tc?.function?.name) parts.push(tc.function.name);
|
|
2062
|
-
if (tc?.function?.arguments) {
|
|
2063
|
-
parts.push(
|
|
2064
|
-
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2065
|
-
);
|
|
2066
|
-
}
|
|
2067
|
-
if (tc?.name && !tc?.function?.name) parts.push(tc.name);
|
|
2068
|
-
if (tc?.args && !tc?.function?.arguments) {
|
|
2069
|
-
parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
|
|
2070
|
-
}
|
|
2071
|
-
}
|
|
2881
|
+
// src/agent.ts
|
|
2882
|
+
function isSyntheticSummaryMessage(message) {
|
|
2883
|
+
if (!message) return false;
|
|
2884
|
+
if (message.role === "tool" && message.name === "summarize_context") {
|
|
2885
|
+
return true;
|
|
2072
2886
|
}
|
|
2073
|
-
if (Array.isArray(
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2079
|
-
);
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2887
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
2888
|
+
return message.tool_calls.some((toolCall) => {
|
|
2889
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
2890
|
+
return toolName === "summarize_context";
|
|
2891
|
+
});
|
|
2082
2892
|
}
|
|
2083
|
-
return
|
|
2084
|
-
}
|
|
2085
|
-
function countMessagesTokens(messages) {
|
|
2086
|
-
if (!Array.isArray(messages) || messages.length === 0) return 0;
|
|
2087
|
-
const allText = messages.map(extractMessageText).join("\n");
|
|
2088
|
-
return countApproxTokens(allText) + messages.length * 4;
|
|
2893
|
+
return false;
|
|
2089
2894
|
}
|
|
2090
|
-
|
|
2091
|
-
// src/agent.ts
|
|
2092
2895
|
function createAgent(opts) {
|
|
2093
2896
|
const resolver = createResolverNode();
|
|
2094
2897
|
const agentCore = createAgentCoreNode(opts);
|
|
@@ -2131,6 +2934,7 @@ function createAgent(opts) {
|
|
|
2131
2934
|
tools: toolsBase,
|
|
2132
2935
|
guardrails: opts.guardrails,
|
|
2133
2936
|
systemPrompt: void 0,
|
|
2937
|
+
todoListPrompt: opts.todoListPrompt,
|
|
2134
2938
|
limits: opts.limits,
|
|
2135
2939
|
useTodoList: void 0,
|
|
2136
2940
|
outputSchema: opts.outputSchema,
|
|
@@ -2206,6 +3010,18 @@ function createAgent(opts) {
|
|
|
2206
3010
|
};
|
|
2207
3011
|
while (iterations < iterationLimit) {
|
|
2208
3012
|
iterations++;
|
|
3013
|
+
const traceSession = state.ctx?.__traceSession;
|
|
3014
|
+
if (traceSession) {
|
|
3015
|
+
const iterEvent = recordTraceEvent(traceSession, {
|
|
3016
|
+
type: "agent_iteration",
|
|
3017
|
+
label: `Iteration ${iterations}`,
|
|
3018
|
+
actor: { scope: "agent", name: opts.name || "agent", role: "orchestrator" },
|
|
3019
|
+
parentSpanId: traceSession.rootSpanId
|
|
3020
|
+
});
|
|
3021
|
+
if (iterEvent) {
|
|
3022
|
+
traceSession.currentIterationSpanId = iterEvent.spanId;
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
2209
3025
|
if (cancelIfRequested("loop")) break;
|
|
2210
3026
|
const skippingAgent = resumeStage === "tools";
|
|
2211
3027
|
if (!skippingAgent) {
|
|
@@ -2256,11 +3072,21 @@ function createAgent(opts) {
|
|
|
2256
3072
|
maxTok = 5e4;
|
|
2257
3073
|
}
|
|
2258
3074
|
if (maxTok) {
|
|
2259
|
-
const
|
|
3075
|
+
const tokCountMessages = (state.messages || []).filter((message) => {
|
|
3076
|
+
if (isSyntheticSummaryMessage(message)) return false;
|
|
3077
|
+
if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
|
|
3078
|
+
return true;
|
|
3079
|
+
});
|
|
3080
|
+
const tokenCount = countMessagesTokens(tokCountMessages);
|
|
2260
3081
|
if (tokenCount > maxTok) {
|
|
2261
|
-
const
|
|
2262
|
-
|
|
2263
|
-
|
|
3082
|
+
const hasFreshSummary = (state.messages || []).some(
|
|
3083
|
+
(m) => m.role === "tool" && m.content === "SUMMARIZED"
|
|
3084
|
+
);
|
|
3085
|
+
if (hasFreshSummary && tokenCount <= maxTok * 1.15) ; else {
|
|
3086
|
+
const ctx = { ...state.ctx || {}, __needsSummarization: true };
|
|
3087
|
+
state = { ...state, ctx };
|
|
3088
|
+
break;
|
|
3089
|
+
}
|
|
2264
3090
|
}
|
|
2265
3091
|
}
|
|
2266
3092
|
onProgress?.({ stage: "agent", message: "Invoking model" });
|
|
@@ -2498,7 +3324,11 @@ function createAgent(opts) {
|
|
|
2498
3324
|
schema,
|
|
2499
3325
|
func: async ({ input }) => {
|
|
2500
3326
|
const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
|
|
2501
|
-
return {
|
|
3327
|
+
return {
|
|
3328
|
+
content: res.content,
|
|
3329
|
+
output: res.output,
|
|
3330
|
+
summary: res.state?.summaries?.[res.state.summaries.length - 1]
|
|
3331
|
+
};
|
|
2502
3332
|
}
|
|
2503
3333
|
});
|
|
2504
3334
|
},
|
|
@@ -2539,6 +3369,7 @@ function createAgentNode(opts) {
|
|
|
2539
3369
|
model: opts.model,
|
|
2540
3370
|
tools: opts.tools || [],
|
|
2541
3371
|
systemPrompt: opts.systemPrompt,
|
|
3372
|
+
todoListPrompt: opts.todoListPrompt,
|
|
2542
3373
|
limits: opts.limits,
|
|
2543
3374
|
useTodoList: opts.useTodoList,
|
|
2544
3375
|
outputSchema: opts.outputSchema
|
|
@@ -2556,7 +3387,8 @@ function createAgentNode(opts) {
|
|
|
2556
3387
|
content: buildSystemPrompt(
|
|
2557
3388
|
[runtime.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
|
|
2558
3389
|
runtime.useTodoList === true,
|
|
2559
|
-
runtime.name || "Agent"
|
|
3390
|
+
runtime.name || "Agent",
|
|
3391
|
+
runtime.todoListPrompt
|
|
2560
3392
|
)
|
|
2561
3393
|
};
|
|
2562
3394
|
const messages = [systemMsg, ...state.messages];
|
|
@@ -2638,9 +3470,119 @@ function createAgentNode(opts) {
|
|
|
2638
3470
|
};
|
|
2639
3471
|
}
|
|
2640
3472
|
|
|
3473
|
+
// src/smart/contextPolicy.ts
|
|
3474
|
+
function messageText(message) {
|
|
3475
|
+
if (typeof message.content === "string") return message.content;
|
|
3476
|
+
if (Array.isArray(message.content)) {
|
|
3477
|
+
return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? JSON.stringify(part)).join(" ");
|
|
3478
|
+
}
|
|
3479
|
+
return "";
|
|
3480
|
+
}
|
|
3481
|
+
function collectRecentTurns(messages, lastTurnsToKeep) {
|
|
3482
|
+
if (lastTurnsToKeep <= 0) return [];
|
|
3483
|
+
const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
|
|
3484
|
+
const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
|
|
3485
|
+
let userTurnCount = 0;
|
|
3486
|
+
const collected = [];
|
|
3487
|
+
for (let index = body.length - 1; index >= 0; index -= 1) {
|
|
3488
|
+
const message = body[index];
|
|
3489
|
+
collected.unshift(message);
|
|
3490
|
+
if (message.role === "user") {
|
|
3491
|
+
userTurnCount += 1;
|
|
3492
|
+
if (userTurnCount >= lastTurnsToKeep) break;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
return [...systemPrefix, ...collected];
|
|
3496
|
+
}
|
|
3497
|
+
function renderStructuredSummary(summary) {
|
|
3498
|
+
if (!summary) return "";
|
|
3499
|
+
const lines = ["Context summary:"];
|
|
3500
|
+
if (summary.stable_facts.length > 0) {
|
|
3501
|
+
lines.push("Stable facts:");
|
|
3502
|
+
lines.push(...summary.stable_facts.map((fact) => `- ${fact.key}: ${fact.value}`));
|
|
3503
|
+
}
|
|
3504
|
+
if (summary.active_goals.length > 0) {
|
|
3505
|
+
lines.push("Active goals:");
|
|
3506
|
+
lines.push(...summary.active_goals.map((goal) => `- ${goal}`));
|
|
3507
|
+
}
|
|
3508
|
+
if (summary.open_questions.length > 0) {
|
|
3509
|
+
lines.push("Open questions:");
|
|
3510
|
+
lines.push(...summary.open_questions.map((question) => `- ${question}`));
|
|
3511
|
+
}
|
|
3512
|
+
if (summary.discarded_obsolete.length > 0) {
|
|
3513
|
+
lines.push("Discarded obsolete:");
|
|
3514
|
+
lines.push(...summary.discarded_obsolete.map((item) => `- ${item}`));
|
|
3515
|
+
}
|
|
3516
|
+
return lines.join("\n");
|
|
3517
|
+
}
|
|
3518
|
+
function renderMemoryBlock(facts) {
|
|
3519
|
+
if (!facts || facts.length === 0) return "";
|
|
3520
|
+
return [
|
|
3521
|
+
"Retrieved memory:",
|
|
3522
|
+
...facts.map((fact) => `- ${fact.key}: ${fact.value}`)
|
|
3523
|
+
].join("\n");
|
|
3524
|
+
}
|
|
3525
|
+
function clampToBudget(messages, maxContextTokens) {
|
|
3526
|
+
let working = [...messages];
|
|
3527
|
+
while (working.length > 2) {
|
|
3528
|
+
const tokenCount = countApproxTokens(working.map(messageText).join("\n"));
|
|
3529
|
+
if (tokenCount <= maxContextTokens) return working;
|
|
3530
|
+
const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
|
|
3531
|
+
if (firstNonSystem < 0) break;
|
|
3532
|
+
working.splice(firstNonSystem, 1);
|
|
3533
|
+
}
|
|
3534
|
+
return working;
|
|
3535
|
+
}
|
|
3536
|
+
function buildModelMessages(state, config) {
|
|
3537
|
+
const rawMessages = state.messages || [];
|
|
3538
|
+
if (config.context.policy === "raw") {
|
|
3539
|
+
return clampToBudget(rawMessages, config.limits.maxContextTokens);
|
|
3540
|
+
}
|
|
3541
|
+
const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
|
|
3542
|
+
const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
|
|
3543
|
+
const summaryText = renderStructuredSummary(latestSummary);
|
|
3544
|
+
const memoryText = renderMemoryBlock(state.memoryFacts);
|
|
3545
|
+
const systemMessage2 = recentMessages[0]?.role === "system" ? recentMessages[0] : void 0;
|
|
3546
|
+
const body = systemMessage2 ? recentMessages.slice(1) : recentMessages;
|
|
3547
|
+
const syntheticContextMessages = [];
|
|
3548
|
+
if (summaryText) {
|
|
3549
|
+
syntheticContextMessages.push({ role: "system", name: "context_summary", content: summaryText });
|
|
3550
|
+
}
|
|
3551
|
+
if (memoryText) {
|
|
3552
|
+
syntheticContextMessages.push({ role: "system", name: "memory_context", content: memoryText });
|
|
3553
|
+
}
|
|
3554
|
+
const assembled = [
|
|
3555
|
+
...systemMessage2 ? [systemMessage2] : [],
|
|
3556
|
+
...syntheticContextMessages,
|
|
3557
|
+
...config.context.policy === "summary_only" ? body.filter((message) => message.role === "user" || message.role === "assistant").slice(-2) : body
|
|
3558
|
+
];
|
|
3559
|
+
return clampToBudget(assembled, config.limits.maxContextTokens);
|
|
3560
|
+
}
|
|
3561
|
+
function estimateContextRotScore(state) {
|
|
3562
|
+
const summaryCount = state.summaryRecords?.length || 0;
|
|
3563
|
+
const archivedCount = state.toolHistoryArchived?.length || 0;
|
|
3564
|
+
const messageCount = state.messages?.length || 0;
|
|
3565
|
+
const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && message.content === "SUMMARIZED").length || 0;
|
|
3566
|
+
const numerator = summaryCount * 2 + archivedCount + summarizedMessages;
|
|
3567
|
+
const denominator = Math.max(messageCount, 1);
|
|
3568
|
+
return Number((numerator / denominator).toFixed(3));
|
|
3569
|
+
}
|
|
3570
|
+
|
|
2641
3571
|
// src/nodes/contextSummarize.ts
|
|
2642
3572
|
var systemMessage = (content) => ({ role: "system", content });
|
|
2643
3573
|
var humanMessage = (content) => ({ role: "user", content });
|
|
3574
|
+
function isSyntheticSummaryToolMessage(message) {
|
|
3575
|
+
return message.role === "tool" && message.name === "summarize_context";
|
|
3576
|
+
}
|
|
3577
|
+
function isSyntheticSummaryAssistantMessage(message) {
|
|
3578
|
+
if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) {
|
|
3579
|
+
return false;
|
|
3580
|
+
}
|
|
3581
|
+
return message.tool_calls.some((toolCall) => {
|
|
3582
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
3583
|
+
return toolName === "summarize_context";
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
2644
3586
|
function validateMessageSequence(messages) {
|
|
2645
3587
|
const validated = [];
|
|
2646
3588
|
const pendingToolCallIds = /* @__PURE__ */ new Set();
|
|
@@ -2666,33 +3608,137 @@ function validateMessageSequence(messages) {
|
|
|
2666
3608
|
}
|
|
2667
3609
|
return validated;
|
|
2668
3610
|
}
|
|
2669
|
-
function
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
3611
|
+
function extractJsonObject(text) {
|
|
3612
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
3613
|
+
if (fenced?.[1]) return fenced[1].trim();
|
|
3614
|
+
const start = text.indexOf("{");
|
|
3615
|
+
const end = text.lastIndexOf("}");
|
|
3616
|
+
if (start >= 0 && end > start) {
|
|
3617
|
+
return text.slice(start, end + 1);
|
|
3618
|
+
}
|
|
3619
|
+
return null;
|
|
3620
|
+
}
|
|
3621
|
+
function normalizeStructuredSummary(input, fallbackText) {
|
|
3622
|
+
const summary = {
|
|
3623
|
+
stable_facts: Array.isArray(input?.stable_facts) ? input.stable_facts.filter((fact) => fact && typeof fact === "object").map((fact) => ({
|
|
3624
|
+
key: String(fact.key || "unknown"),
|
|
3625
|
+
value: String(fact.value || ""),
|
|
3626
|
+
confidence: typeof fact.confidence === "number" ? fact.confidence : 0.7,
|
|
3627
|
+
source: fact.source ? String(fact.source) : void 0
|
|
3628
|
+
})) : [],
|
|
3629
|
+
active_goals: Array.isArray(input?.active_goals) ? input.active_goals.map((goal) => String(goal)) : [],
|
|
3630
|
+
open_questions: Array.isArray(input?.open_questions) ? input.open_questions.map((question) => String(question)) : [],
|
|
3631
|
+
discarded_obsolete: Array.isArray(input?.discarded_obsolete || input?.obsolete_discarded) ? (input.discarded_obsolete || input.obsolete_discarded).map((item) => String(item)) : [],
|
|
3632
|
+
rawSummary: typeof input?.rawSummary === "string" ? input.rawSummary : fallbackText
|
|
3633
|
+
};
|
|
3634
|
+
if (summary.stable_facts.length === 0 && fallbackText) {
|
|
3635
|
+
const importantLines = fallbackText.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 8);
|
|
3636
|
+
summary.stable_facts = importantLines.map((line, index) => ({
|
|
3637
|
+
key: `fact_${index + 1}`,
|
|
3638
|
+
value: line,
|
|
3639
|
+
confidence: 0.5
|
|
3640
|
+
}));
|
|
3641
|
+
}
|
|
3642
|
+
return summary;
|
|
3643
|
+
}
|
|
3644
|
+
function normalizeFactKeySegment(value) {
|
|
3645
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "item";
|
|
3646
|
+
}
|
|
3647
|
+
function extractCanonicalFacts(messages) {
|
|
3648
|
+
const facts = /* @__PURE__ */ new Map();
|
|
3649
|
+
for (const message of messages) {
|
|
3650
|
+
const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.map((part) => part?.text ?? part?.content ?? String(part ?? "")).join("\n") : "";
|
|
3651
|
+
if (!content) continue;
|
|
3652
|
+
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3653
|
+
for (const line of lines) {
|
|
3654
|
+
if (!/^[A-Z0-9_]+\|/.test(line) || !line.includes("=")) continue;
|
|
3655
|
+
const segments = line.split("|").map((segment) => segment.trim()).filter(Boolean);
|
|
3656
|
+
const label = segments[0];
|
|
3657
|
+
const entries = segments.slice(1).map((segment) => {
|
|
3658
|
+
const eqIndex = segment.indexOf("=");
|
|
3659
|
+
if (eqIndex <= 0) return null;
|
|
3660
|
+
return {
|
|
3661
|
+
key: segment.slice(0, eqIndex).trim(),
|
|
3662
|
+
value: segment.slice(eqIndex + 1).trim()
|
|
3663
|
+
};
|
|
3664
|
+
}).filter((entry) => Boolean(entry?.key));
|
|
3665
|
+
if (entries.length === 0) continue;
|
|
3666
|
+
const identifier = entries.find((entry) => ["code", "id", "project", "name", "key"].includes(entry.key.toLowerCase()))?.value;
|
|
3667
|
+
const keyPrefix = [normalizeFactKeySegment(label), identifier ? normalizeFactKeySegment(identifier) : void 0].filter(Boolean).join(".");
|
|
3668
|
+
for (const entry of entries) {
|
|
3669
|
+
const normalizedEntryKey = normalizeFactKeySegment(entry.key);
|
|
3670
|
+
const factKey = keyPrefix ? `${keyPrefix}.${normalizedEntryKey}` : `${normalizeFactKeySegment(label)}.${normalizedEntryKey}`;
|
|
3671
|
+
facts.set(factKey, {
|
|
3672
|
+
key: factKey,
|
|
3673
|
+
value: entry.value,
|
|
3674
|
+
confidence: 0.98,
|
|
3675
|
+
source: "canonical_tool_output"
|
|
3676
|
+
});
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
return [...facts.values()];
|
|
3681
|
+
}
|
|
3682
|
+
function mergeStableFacts(summary, additionalFacts) {
|
|
3683
|
+
if (additionalFacts.length === 0) return summary;
|
|
3684
|
+
const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
|
|
3685
|
+
for (const fact of additionalFacts) {
|
|
3686
|
+
const existing = merged.get(fact.key);
|
|
3687
|
+
if (!existing || (fact.confidence || 0) >= (existing.confidence || 0)) {
|
|
3688
|
+
merged.set(fact.key, fact);
|
|
3689
|
+
}
|
|
2679
3690
|
}
|
|
2680
3691
|
return {
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
summaryPromptMaxTokens: 8e3,
|
|
2684
|
-
promptTemplate: void 0
|
|
3692
|
+
...summary,
|
|
3693
|
+
stable_facts: [...merged.values()]
|
|
2685
3694
|
};
|
|
2686
3695
|
}
|
|
3696
|
+
function runIntegrityCheck(previous, current) {
|
|
3697
|
+
const notes = [];
|
|
3698
|
+
const previousKeys = new Set((previous?.stable_facts || []).map((fact) => fact.key));
|
|
3699
|
+
const currentKeys = new Set(current.stable_facts.map((fact) => fact.key));
|
|
3700
|
+
const obsoleteKeys = new Set(current.discarded_obsolete);
|
|
3701
|
+
const criticalFactLoss = [...previousKeys].some((key) => !obsoleteKeys.has(key) && !currentKeys.has(key));
|
|
3702
|
+
const obsoleteFactRevived = current.stable_facts.some((fact) => obsoleteKeys.has(fact.key));
|
|
3703
|
+
if (criticalFactLoss) {
|
|
3704
|
+
notes.push("Missing previously retained facts; merged forward during integrity repair.");
|
|
3705
|
+
}
|
|
3706
|
+
if (obsoleteFactRevived) {
|
|
3707
|
+
notes.push("Obsolete facts reappeared; removed during integrity repair.");
|
|
3708
|
+
}
|
|
3709
|
+
return {
|
|
3710
|
+
passed: !criticalFactLoss && !obsoleteFactRevived,
|
|
3711
|
+
criticalFactLoss,
|
|
3712
|
+
obsoleteFactRevived,
|
|
3713
|
+
notes
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
function repairStructuredSummary(previous, current, integrity) {
|
|
3717
|
+
if (!previous) return current;
|
|
3718
|
+
const nextFacts = [...current.stable_facts];
|
|
3719
|
+
if (integrity.criticalFactLoss) {
|
|
3720
|
+
const existingKeys = new Set(nextFacts.map((fact) => fact.key));
|
|
3721
|
+
for (const fact of previous.stable_facts) {
|
|
3722
|
+
if (!existingKeys.has(fact.key) && !current.discarded_obsolete.includes(fact.key)) {
|
|
3723
|
+
nextFacts.push(fact);
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
const filteredFacts = integrity.obsoleteFactRevived ? nextFacts.filter((fact) => !current.discarded_obsolete.includes(fact.key)) : nextFacts;
|
|
3728
|
+
return { ...current, stable_facts: filteredFacts };
|
|
3729
|
+
}
|
|
2687
3730
|
function createContextSummarizeNode(opts) {
|
|
2688
3731
|
return async (state) => {
|
|
2689
|
-
const
|
|
2690
|
-
|
|
3732
|
+
const resolved = getResolvedSmartConfig(opts, state.agent);
|
|
3733
|
+
const config = resolved.summarization;
|
|
3734
|
+
if (!config.enable) return {};
|
|
2691
3735
|
const model = opts.model;
|
|
2692
3736
|
const messages = state.messages || [];
|
|
2693
3737
|
const onEvent = state.ctx?.__onEvent;
|
|
2694
3738
|
const traceSession = state.ctx?.__traceSession;
|
|
2695
|
-
const compressableMessages = messages.filter(
|
|
3739
|
+
const compressableMessages = messages.filter(
|
|
3740
|
+
(m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
|
|
3741
|
+
);
|
|
2696
3742
|
if (compressableMessages.length === 0) {
|
|
2697
3743
|
return {};
|
|
2698
3744
|
}
|
|
@@ -2704,6 +3750,9 @@ function createContextSummarizeNode(opts) {
|
|
|
2704
3750
|
let accumulatedTokens = 0;
|
|
2705
3751
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2706
3752
|
const m = messages[i];
|
|
3753
|
+
if (isSyntheticSummaryToolMessage(m) || isSyntheticSummaryAssistantMessage(m)) {
|
|
3754
|
+
continue;
|
|
3755
|
+
}
|
|
2707
3756
|
let content = "";
|
|
2708
3757
|
if (typeof m.content === "string") content = m.content;
|
|
2709
3758
|
else if (Array.isArray(m.content)) {
|
|
@@ -2715,39 +3764,60 @@ function createContextSummarizeNode(opts) {
|
|
|
2715
3764
|
}
|
|
2716
3765
|
let prefix = `${m.role.toUpperCase()}`;
|
|
2717
3766
|
if (m.name) prefix += ` (${m.name})`;
|
|
2718
|
-
const
|
|
2719
|
-
const messageTokens = countApproxTokens(
|
|
3767
|
+
const messageText2 = `${prefix}: ${content}`;
|
|
3768
|
+
const messageTokens = countApproxTokens(messageText2);
|
|
2720
3769
|
if (accumulatedTokens + messageTokens > availableTokensForConversation) {
|
|
2721
3770
|
messageTexts.unshift("[Earlier conversation history omitted for brevity]");
|
|
2722
3771
|
break;
|
|
2723
3772
|
}
|
|
2724
|
-
messageTexts.unshift(
|
|
3773
|
+
messageTexts.unshift(messageText2);
|
|
2725
3774
|
accumulatedTokens += messageTokens;
|
|
2726
3775
|
}
|
|
2727
3776
|
const conversationText = messageTexts.join("\n\n");
|
|
3777
|
+
const canonicalFacts = extractCanonicalFacts(compressableMessages);
|
|
2728
3778
|
const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
|
|
2729
|
-
const
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
3779
|
+
const previousStructuredSummary = Array.isArray(state.summaryRecords) && state.summaryRecords.length > 0 ? state.summaryRecords[state.summaryRecords.length - 1] : void 0;
|
|
3780
|
+
const defaultPrompt = `Summarize the conversation into strict JSON.
|
|
3781
|
+
|
|
3782
|
+
Return exactly one JSON object with this schema:
|
|
3783
|
+
{
|
|
3784
|
+
"stable_facts": [{ "key": string, "value": string, "confidence": number }],
|
|
3785
|
+
"active_goals": [string],
|
|
3786
|
+
"open_questions": [string],
|
|
3787
|
+
"discarded_obsolete": [string],
|
|
3788
|
+
"rawSummary": string
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
Rules:
|
|
3792
|
+
- Keep stable_facts only for facts that future turns must remember.
|
|
3793
|
+
- Put invalidated or superseded fact keys in discarded_obsolete.
|
|
3794
|
+
- Preserve active_goals still relevant to the user.
|
|
3795
|
+
- Preserve unresolved questions.
|
|
3796
|
+
- Do not include prose outside JSON.
|
|
3797
|
+
- Target compression ratio: ${config.summaryCompressionRatioTarget}.
|
|
3798
|
+
- Summary mode: ${config.summaryMode}.
|
|
2735
3799
|
|
|
2736
|
-
Previous summary
|
|
3800
|
+
Previous summary text:
|
|
2737
3801
|
${previousSummary || "(none)"}
|
|
2738
3802
|
|
|
3803
|
+
Previous structured summary:
|
|
3804
|
+
${previousStructuredSummary ? JSON.stringify(previousStructuredSummary) : "(none)"}
|
|
3805
|
+
|
|
2739
3806
|
Conversation:
|
|
2740
3807
|
${conversationText}
|
|
2741
3808
|
|
|
2742
|
-
|
|
3809
|
+
Canonical facts extracted from tool outputs:
|
|
3810
|
+
${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fact.value}`).join("\n") : "(none)"}`;
|
|
2743
3811
|
const template = config.promptTemplate || defaultPrompt;
|
|
2744
3812
|
const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
|
|
2745
3813
|
const summaryPrompt = [
|
|
2746
|
-
systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
|
|
3814
|
+
systemMessage("You are a helpful assistant that summarizes conversation history efficiently. Return strict JSON only."),
|
|
2747
3815
|
humanMessage(promptBody)
|
|
2748
3816
|
];
|
|
2749
3817
|
const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
|
|
2750
3818
|
let summaryText = "Summary unavailable.";
|
|
3819
|
+
let structuredSummary;
|
|
3820
|
+
let integrity;
|
|
2751
3821
|
let outputTokensEstimate = 0;
|
|
2752
3822
|
let durationMs = 0;
|
|
2753
3823
|
let inputTokensActual;
|
|
@@ -2756,10 +3826,23 @@ Summary:`;
|
|
|
2756
3826
|
let totalTokensActual;
|
|
2757
3827
|
const startTime = Date.now();
|
|
2758
3828
|
try {
|
|
3829
|
+
if (!model || typeof model.invoke !== "function") {
|
|
3830
|
+
throw new Error("Summarization model is unavailable.");
|
|
3831
|
+
}
|
|
2759
3832
|
const response = await model.invoke(summaryPrompt);
|
|
2760
3833
|
durationMs = Date.now() - startTime;
|
|
2761
3834
|
summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
|
|
2762
3835
|
outputTokensEstimate = countApproxTokens(summaryText);
|
|
3836
|
+
const jsonText = extractJsonObject(summaryText);
|
|
3837
|
+
const parsed = jsonText ? JSON.parse(jsonText) : void 0;
|
|
3838
|
+
structuredSummary = normalizeStructuredSummary(parsed, summaryText);
|
|
3839
|
+
structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
|
|
3840
|
+
integrity = config.integrityCheck ? runIntegrityCheck(previousStructuredSummary, structuredSummary) : { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: [] };
|
|
3841
|
+
if (integrity && !integrity.passed) {
|
|
3842
|
+
structuredSummary = repairStructuredSummary(previousStructuredSummary, structuredSummary, integrity);
|
|
3843
|
+
integrity = { ...integrity, passed: true };
|
|
3844
|
+
}
|
|
3845
|
+
summaryText = renderStructuredSummary(structuredSummary);
|
|
2763
3846
|
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
|
|
2764
3847
|
const normalized = normalizeUsage(rawUsage);
|
|
2765
3848
|
if (normalized) {
|
|
@@ -2770,17 +3853,22 @@ Summary:`;
|
|
|
2770
3853
|
}
|
|
2771
3854
|
} catch (err) {
|
|
2772
3855
|
durationMs = Date.now() - startTime;
|
|
2773
|
-
|
|
3856
|
+
structuredSummary = normalizeStructuredSummary(void 0, previousSummary || conversationText.slice(0, 800));
|
|
3857
|
+
structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
|
|
3858
|
+
integrity = { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: ["Fallback summary generated locally."] };
|
|
3859
|
+
summaryText = renderStructuredSummary(structuredSummary);
|
|
2774
3860
|
const errorEvent = {
|
|
2775
3861
|
type: "summarization",
|
|
2776
|
-
summary:
|
|
2777
|
-
messagesCompressed:
|
|
3862
|
+
summary: summaryText,
|
|
3863
|
+
messagesCompressed: compressableMessages.length,
|
|
2778
3864
|
inputTokens: inputTokensEstimate,
|
|
2779
|
-
outputTokens:
|
|
3865
|
+
outputTokens: countApproxTokens(summaryText),
|
|
2780
3866
|
durationMs,
|
|
2781
3867
|
previousSummary: previousSummary || void 0,
|
|
2782
3868
|
tokenCountBefore,
|
|
2783
|
-
tokenCountAfter: tokenCountBefore
|
|
3869
|
+
tokenCountAfter: tokenCountBefore,
|
|
3870
|
+
structuredSummary,
|
|
3871
|
+
integrity
|
|
2784
3872
|
};
|
|
2785
3873
|
onEvent?.(errorEvent);
|
|
2786
3874
|
recordTraceEvent(traceSession, {
|
|
@@ -2790,11 +3878,10 @@ Summary:`;
|
|
|
2790
3878
|
status: "error",
|
|
2791
3879
|
durationMs,
|
|
2792
3880
|
inputTokens: inputTokensEstimate,
|
|
2793
|
-
outputTokens:
|
|
3881
|
+
outputTokens: countApproxTokens(summaryText),
|
|
2794
3882
|
error: { message: err?.message || String(err), stack: err?.stack },
|
|
2795
3883
|
messageList: sanitizeTracePayload(summaryPrompt)
|
|
2796
3884
|
});
|
|
2797
|
-
return {};
|
|
2798
3885
|
}
|
|
2799
3886
|
const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
|
|
2800
3887
|
messages.forEach((m, idx) => {
|
|
@@ -2808,6 +3895,9 @@ Summary:`;
|
|
|
2808
3895
|
});
|
|
2809
3896
|
const newMessages = messages.map((m) => {
|
|
2810
3897
|
if (m.role === "tool") {
|
|
3898
|
+
if (isSyntheticSummaryToolMessage(m)) {
|
|
3899
|
+
return m;
|
|
3900
|
+
}
|
|
2811
3901
|
const toolCallId = m.tool_call_id;
|
|
2812
3902
|
if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
|
|
2813
3903
|
return null;
|
|
@@ -2845,6 +3935,7 @@ Summary:`;
|
|
|
2845
3935
|
content: summaryText
|
|
2846
3936
|
};
|
|
2847
3937
|
const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
|
|
3938
|
+
const summaryRecords = Array.isArray(state.summaryRecords) ? [...state.summaryRecords, { ...structuredSummary || normalizeStructuredSummary(void 0, summaryText), integrity, createdAt: (/* @__PURE__ */ new Date()).toISOString() }] : [{ ...structuredSummary || normalizeStructuredSummary(void 0, summaryText), integrity, createdAt: (/* @__PURE__ */ new Date()).toISOString() }];
|
|
2848
3939
|
const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
|
|
2849
3940
|
const allTextAfter = finalMessages.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");
|
|
2850
3941
|
const tokenCountAfter = countApproxTokens(allTextAfter);
|
|
@@ -2860,8 +3951,10 @@ Summary:`;
|
|
|
2860
3951
|
previousSummary: previousSummary || void 0,
|
|
2861
3952
|
tokenCountBefore,
|
|
2862
3953
|
tokenCountAfter,
|
|
2863
|
-
archivedCount: compressableMessages.length
|
|
3954
|
+
archivedCount: compressableMessages.length,
|
|
2864
3955
|
// deprecated field for backward compatibility
|
|
3956
|
+
structuredSummary,
|
|
3957
|
+
integrity
|
|
2865
3958
|
};
|
|
2866
3959
|
onEvent?.(summarizationEvent);
|
|
2867
3960
|
recordTraceEvent(traceSession, {
|
|
@@ -2885,12 +3978,19 @@ Summary:`;
|
|
|
2885
3978
|
tokensSaved: tokenCountBefore - tokenCountAfter,
|
|
2886
3979
|
previousSummaryLength: previousSummary?.length || 0,
|
|
2887
3980
|
usageFromModel: inputTokensActual !== void 0,
|
|
2888
|
-
cachedInputTokens
|
|
3981
|
+
cachedInputTokens,
|
|
3982
|
+
integrity
|
|
2889
3983
|
}
|
|
2890
3984
|
});
|
|
2891
3985
|
return {
|
|
2892
3986
|
messages: finalMessages,
|
|
2893
|
-
summaries
|
|
3987
|
+
summaries,
|
|
3988
|
+
summaryRecords,
|
|
3989
|
+
watchdog: {
|
|
3990
|
+
...state.watchdog || {},
|
|
3991
|
+
compactions: (state.watchdog?.compactions || 0) + 1,
|
|
3992
|
+
lastAction: "summarized"
|
|
3993
|
+
}
|
|
2894
3994
|
};
|
|
2895
3995
|
};
|
|
2896
3996
|
}
|
|
@@ -2913,38 +4013,200 @@ async function applyTokenLimits({
|
|
|
2913
4013
|
}
|
|
2914
4014
|
return { ...state, messages: newMessages };
|
|
2915
4015
|
}
|
|
4016
|
+
var todoStatusSchema = zod.z.enum(["not-started", "in-progress", "completed", "blocked"]);
|
|
4017
|
+
var todoWriteItemSchema = zod.z.object({
|
|
4018
|
+
id: zod.z.number().int().min(1).describe("Sequential id starting from 1"),
|
|
4019
|
+
step: zod.z.string().min(1).optional(),
|
|
4020
|
+
title: zod.z.string().min(1).optional(),
|
|
4021
|
+
description: zod.z.string().min(1).optional(),
|
|
4022
|
+
owner: zod.z.string().min(1).optional(),
|
|
4023
|
+
exitCriteria: zod.z.string().min(1).optional(),
|
|
4024
|
+
status: todoStatusSchema,
|
|
4025
|
+
evidence: zod.z.string().max(300).optional()
|
|
4026
|
+
});
|
|
4027
|
+
var todoUpdateItemSchema = zod.z.object({
|
|
4028
|
+
id: zod.z.number().int().min(1).describe("Existing todo id to update"),
|
|
4029
|
+
step: zod.z.string().min(1).optional(),
|
|
4030
|
+
title: zod.z.string().min(1).optional(),
|
|
4031
|
+
description: zod.z.string().min(1).optional(),
|
|
4032
|
+
owner: zod.z.string().min(1).optional(),
|
|
4033
|
+
exitCriteria: zod.z.string().min(1).optional(),
|
|
4034
|
+
status: todoStatusSchema.optional(),
|
|
4035
|
+
evidence: zod.z.string().max(300).optional()
|
|
4036
|
+
});
|
|
4037
|
+
function normalizeTodoItem(item, existing) {
|
|
4038
|
+
const id = item.id ?? existing?.id;
|
|
4039
|
+
const step = item.step || item.title || item.description || existing?.step || existing?.title || existing?.description || `Step ${id}`;
|
|
4040
|
+
const title = item.title || item.step || existing?.title || existing?.step || `Step ${id}`;
|
|
4041
|
+
const description = item.description || item.step || item.title || existing?.description || existing?.step || existing?.title || `Step ${id}`;
|
|
4042
|
+
return {
|
|
4043
|
+
...existing,
|
|
4044
|
+
...item,
|
|
4045
|
+
id,
|
|
4046
|
+
step,
|
|
4047
|
+
title,
|
|
4048
|
+
description,
|
|
4049
|
+
owner: item.owner || existing?.owner || "agent",
|
|
4050
|
+
exitCriteria: item.exitCriteria || existing?.exitCriteria || description || `Complete step ${id}`,
|
|
4051
|
+
status: item.status || existing?.status,
|
|
4052
|
+
evidence: item.evidence ?? existing?.evidence
|
|
4053
|
+
};
|
|
4054
|
+
}
|
|
4055
|
+
function validatePlanInvariants(todoList, requireSequentialIds) {
|
|
4056
|
+
const ids = todoList.map((item) => item.id);
|
|
4057
|
+
const uniqueIds = new Set(ids);
|
|
4058
|
+
if (uniqueIds.size !== ids.length) {
|
|
4059
|
+
return { ok: false, error: "Todo ids must be unique." };
|
|
4060
|
+
}
|
|
4061
|
+
if (requireSequentialIds) {
|
|
4062
|
+
const expectedIds = Array.from({ length: todoList.length }, (_, index) => index + 1);
|
|
4063
|
+
const isSequential = ids.every((id, index) => id === expectedIds[index]);
|
|
4064
|
+
if (!isSequential) {
|
|
4065
|
+
return { ok: false, error: "Todo ids must be sequential starting from 1 for write operations." };
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
const inProgressCount = todoList.filter((item) => item.status === "in-progress").length;
|
|
4069
|
+
if (inProgressCount > 1) {
|
|
4070
|
+
return { ok: false, error: "Only one todo item may be in-progress at a time." };
|
|
4071
|
+
}
|
|
4072
|
+
return { ok: true };
|
|
4073
|
+
}
|
|
4074
|
+
function calculateAdherenceScore(todoList) {
|
|
4075
|
+
const completedCount = todoList.filter((item) => item.status === "completed").length;
|
|
4076
|
+
return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
|
|
4077
|
+
}
|
|
2916
4078
|
function createContextTools(stateRef, opts) {
|
|
2917
4079
|
const tools = [];
|
|
2918
4080
|
if (opts?.planningEnabled) {
|
|
2919
4081
|
const manageTodo = createTool({
|
|
2920
4082
|
name: "manage_todo_list",
|
|
2921
|
-
description: "Manage a structured todo list to track progress and plan tasks throughout your coding session
|
|
4083
|
+
description: "Manage a structured todo list to track progress and plan tasks throughout your coding session.\n\nOperations:\n- read: return the current plan\n- write: replace the full plan with a complete ordered todoList\n- update: patch existing todo items by id without resending the whole plan\n\nRules:\n- Use write only when creating or fully rewriting the plan\n- After a plan exists, prefer update for status, evidence, or owner changes\n- Update payloads should contain only the changed items\n- When using update, pass expectedVersion to avoid overwriting a newer plan\n- Keep ids unique; write operations must keep ids sequential starting from 1\n- Keep at most ONE item in-progress at a time\n- If update fails due to version mismatch, read the latest plan and retry",
|
|
2922
4084
|
schema: zod.z.object({
|
|
2923
|
-
operation: zod.z.enum(["write", "read"]),
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
title: zod.z.string().min(1),
|
|
2927
|
-
description: zod.z.string().min(1),
|
|
2928
|
-
status: zod.z.enum(["not-started", "in-progress", "completed"]),
|
|
2929
|
-
evidence: zod.z.string().max(200).optional()
|
|
2930
|
-
})).optional()
|
|
4085
|
+
operation: zod.z.enum(["write", "read", "update"]),
|
|
4086
|
+
expectedVersion: zod.z.number().int().min(0).optional(),
|
|
4087
|
+
todoList: zod.z.array(zod.z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
|
|
2931
4088
|
}),
|
|
2932
|
-
func: async ({ operation, todoList }) => {
|
|
4089
|
+
func: async ({ operation, expectedVersion, todoList }) => {
|
|
2933
4090
|
const onEvent = manageTodo._stateRef?.__onEvent;
|
|
4091
|
+
const currentVersion = stateRef.planVersion || 0;
|
|
2934
4092
|
if (operation === "read") {
|
|
2935
4093
|
const list = stateRef.todoList || [];
|
|
2936
|
-
onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list });
|
|
4094
|
+
onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
|
|
2937
4095
|
return list;
|
|
2938
4096
|
}
|
|
2939
|
-
if (
|
|
2940
|
-
|
|
4097
|
+
if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
|
|
4098
|
+
return {
|
|
4099
|
+
status: "error",
|
|
4100
|
+
operation,
|
|
4101
|
+
error: "Plan version mismatch. Read the latest plan and retry.",
|
|
4102
|
+
version: currentVersion,
|
|
4103
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
if (!Array.isArray(todoList)) {
|
|
4107
|
+
return {
|
|
4108
|
+
status: "error",
|
|
4109
|
+
operation,
|
|
4110
|
+
error: "todoList is required for write and update operations.",
|
|
4111
|
+
version: currentVersion,
|
|
4112
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4113
|
+
};
|
|
2941
4114
|
}
|
|
4115
|
+
if (operation === "write") {
|
|
4116
|
+
const parsed = zod.z.array(todoWriteItemSchema).safeParse(todoList);
|
|
4117
|
+
if (!parsed.success) {
|
|
4118
|
+
return {
|
|
4119
|
+
status: "error",
|
|
4120
|
+
operation,
|
|
4121
|
+
error: parsed.error.issues[0]?.message || "Invalid todoList for write.",
|
|
4122
|
+
version: currentVersion,
|
|
4123
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
4126
|
+
const normalizedList = parsed.data.map((item) => normalizeTodoItem(item));
|
|
4127
|
+
const validation = validatePlanInvariants(normalizedList, true);
|
|
4128
|
+
if (!validation.ok) {
|
|
4129
|
+
return {
|
|
4130
|
+
status: "error",
|
|
4131
|
+
operation,
|
|
4132
|
+
error: validation.error,
|
|
4133
|
+
version: currentVersion,
|
|
4134
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4137
|
+
stateRef.todoList = normalizedList;
|
|
4138
|
+
} else if (operation === "update") {
|
|
4139
|
+
const parsed = zod.z.array(todoUpdateItemSchema).safeParse(todoList);
|
|
4140
|
+
if (!parsed.success) {
|
|
4141
|
+
return {
|
|
4142
|
+
status: "error",
|
|
4143
|
+
operation,
|
|
4144
|
+
error: parsed.error.issues[0]?.message || "Invalid todoList for update.",
|
|
4145
|
+
version: currentVersion,
|
|
4146
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
const currentList = Array.isArray(stateRef.todoList) ? stateRef.todoList : [];
|
|
4150
|
+
if (currentList.length === 0) {
|
|
4151
|
+
return {
|
|
4152
|
+
status: "error",
|
|
4153
|
+
operation,
|
|
4154
|
+
error: "No existing plan to update. Use write first.",
|
|
4155
|
+
version: currentVersion,
|
|
4156
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4159
|
+
const patchIds = /* @__PURE__ */ new Set();
|
|
4160
|
+
for (const item of parsed.data) {
|
|
4161
|
+
if (patchIds.has(item.id)) {
|
|
4162
|
+
return {
|
|
4163
|
+
status: "error",
|
|
4164
|
+
operation,
|
|
4165
|
+
error: "Update payload contains duplicate todo ids.",
|
|
4166
|
+
version: currentVersion,
|
|
4167
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4168
|
+
};
|
|
4169
|
+
}
|
|
4170
|
+
patchIds.add(item.id);
|
|
4171
|
+
}
|
|
4172
|
+
const currentMap = new Map(currentList.map((item) => [item.id, item]));
|
|
4173
|
+
for (const item of parsed.data) {
|
|
4174
|
+
if (!currentMap.has(item.id)) {
|
|
4175
|
+
return {
|
|
4176
|
+
status: "error",
|
|
4177
|
+
operation,
|
|
4178
|
+
error: `Cannot update missing todo id ${item.id}. Use write to replace the plan.`,
|
|
4179
|
+
version: currentVersion,
|
|
4180
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4181
|
+
};
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
const mergedList = currentList.map((item) => {
|
|
4185
|
+
const patch = parsed.data.find((candidate) => candidate.id === item.id);
|
|
4186
|
+
return patch ? normalizeTodoItem(patch, item) : item;
|
|
4187
|
+
});
|
|
4188
|
+
const validation = validatePlanInvariants(mergedList, false);
|
|
4189
|
+
if (!validation.ok) {
|
|
4190
|
+
return {
|
|
4191
|
+
status: "error",
|
|
4192
|
+
operation,
|
|
4193
|
+
error: validation.error,
|
|
4194
|
+
version: currentVersion,
|
|
4195
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4196
|
+
};
|
|
4197
|
+
}
|
|
4198
|
+
stateRef.todoList = mergedList;
|
|
4199
|
+
}
|
|
4200
|
+
stateRef.planVersion = currentVersion + 1;
|
|
4201
|
+
stateRef.adherenceScore = calculateAdherenceScore(stateRef.todoList || []);
|
|
2942
4202
|
const payload = {
|
|
2943
4203
|
status: "ok",
|
|
2944
4204
|
operation,
|
|
2945
|
-
count: Array.isArray(todoList) ? todoList.length : void 0
|
|
4205
|
+
count: Array.isArray(todoList) ? todoList.length : void 0,
|
|
4206
|
+
version: stateRef.planVersion || 1,
|
|
4207
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
2946
4208
|
};
|
|
2947
|
-
onEvent?.({ type: "plan", source: "manage_todo_list", operation:
|
|
4209
|
+
onEvent?.({ type: "plan", source: "manage_todo_list", operation, todoList: stateRef.todoList, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
|
|
2948
4210
|
return payload;
|
|
2949
4211
|
}
|
|
2950
4212
|
});
|
|
@@ -2953,12 +4215,13 @@ function createContextTools(stateRef, opts) {
|
|
|
2953
4215
|
}
|
|
2954
4216
|
const getTool = createTool({
|
|
2955
4217
|
name: "get_tool_response",
|
|
2956
|
-
description: "RETRIEVE tool execution response: Use this to access the full output of a tool execution that shows as 'SUMMARIZED' in the tool history.",
|
|
4218
|
+
description: "RETRIEVE tool execution response: Use this to access the full output of a tool execution that shows as 'SUMMARIZED' in the tool history. Accepts either the internal executionId or the provider tool call id shown in prior tool traces.",
|
|
2957
4219
|
schema: zod.z.object({ executionId: zod.z.string().describe("Tool execution id") }),
|
|
2958
4220
|
func: async ({ executionId }) => {
|
|
2959
|
-
|
|
4221
|
+
const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
|
|
4222
|
+
let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));
|
|
2960
4223
|
if (!execution) {
|
|
2961
|
-
execution = stateRef.toolHistoryArchived?.find((t) => t
|
|
4224
|
+
execution = stateRef.toolHistoryArchived?.find((t) => matchesExecution(t));
|
|
2962
4225
|
}
|
|
2963
4226
|
if (execution) {
|
|
2964
4227
|
return execution.rawOutput || execution.output;
|
|
@@ -2989,19 +4252,33 @@ function createContextTools(stateRef, opts) {
|
|
|
2989
4252
|
}
|
|
2990
4253
|
|
|
2991
4254
|
// src/graph/decisions.ts
|
|
4255
|
+
function isSyntheticSummaryMessage2(message) {
|
|
4256
|
+
if (!message) return false;
|
|
4257
|
+
if (message.role === "tool" && message.name === "summarize_context") {
|
|
4258
|
+
return true;
|
|
4259
|
+
}
|
|
4260
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
4261
|
+
return message.tool_calls.some((toolCall) => {
|
|
4262
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
4263
|
+
return toolName === "summarize_context";
|
|
4264
|
+
});
|
|
4265
|
+
}
|
|
4266
|
+
return false;
|
|
4267
|
+
}
|
|
2992
4268
|
function needsSummarization(state, opts, summarizationEnabled) {
|
|
2993
4269
|
if (!summarizationEnabled) {
|
|
2994
4270
|
return false;
|
|
2995
4271
|
}
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
maxTok = opts.summarization.maxTokens;
|
|
4272
|
+
if (state.ctx?.__needsSummarization) {
|
|
4273
|
+
return true;
|
|
2999
4274
|
}
|
|
3000
|
-
|
|
3001
|
-
|
|
4275
|
+
const resolved = getResolvedSmartConfig(opts, state.agent);
|
|
4276
|
+
if (!resolved.summarization.enable) {
|
|
4277
|
+
return false;
|
|
3002
4278
|
}
|
|
4279
|
+
const maxTok = resolved.summarization.summaryTriggerTokens;
|
|
3003
4280
|
try {
|
|
3004
|
-
const allText = (state.messages || []).map((m) => {
|
|
4281
|
+
const allText = (state.messages || []).filter((message) => !isSyntheticSummaryMessage2(message)).map((m) => {
|
|
3005
4282
|
if (typeof m.content === "string") return m.content;
|
|
3006
4283
|
if (Array.isArray(m.content)) {
|
|
3007
4284
|
return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
|
|
@@ -3012,7 +4289,8 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
3012
4289
|
return "";
|
|
3013
4290
|
}).join("\n");
|
|
3014
4291
|
const tokenCount = countApproxTokens(allText);
|
|
3015
|
-
const
|
|
4292
|
+
const contextRotScore = state.watchdog?.contextRotScore || 0;
|
|
4293
|
+
const needsSum = tokenCount > maxTok || resolved.watchdog.enabled && resolved.watchdog.autoCompaction && contextRotScore >= resolved.watchdog.contextRotThreshold;
|
|
3016
4294
|
if (process.env.DEBUG_SUMMARIZATION) {
|
|
3017
4295
|
const msgCount = (state.messages || []).length;
|
|
3018
4296
|
const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
|
|
@@ -3022,10 +4300,10 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
3022
4300
|
});
|
|
3023
4301
|
console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
|
|
3024
4302
|
toolDetails.forEach((d) => console.log(` ${d}`));
|
|
3025
|
-
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
|
|
4303
|
+
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Context rot: ${contextRotScore}, Needs summarization: ${needsSum}`);
|
|
3026
4304
|
}
|
|
3027
4305
|
return needsSum;
|
|
3028
|
-
} catch
|
|
4306
|
+
} catch {
|
|
3029
4307
|
return false;
|
|
3030
4308
|
}
|
|
3031
4309
|
}
|
|
@@ -3043,33 +4321,205 @@ function toolsDecisionFactory(opts, summarizationEnabled) {
|
|
|
3043
4321
|
};
|
|
3044
4322
|
}
|
|
3045
4323
|
|
|
4324
|
+
// src/smart/memory.ts
|
|
4325
|
+
function normalizeFact(scope, fact) {
|
|
4326
|
+
return {
|
|
4327
|
+
...fact,
|
|
4328
|
+
scope,
|
|
4329
|
+
obsolete: fact.obsolete ?? false,
|
|
4330
|
+
lastUpdatedAt: fact.lastUpdatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4331
|
+
confidence: Number.isFinite(fact.confidence) ? fact.confidence : 0.5
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
var InMemoryMemoryStore = class {
|
|
4335
|
+
constructor() {
|
|
4336
|
+
this.buckets = /* @__PURE__ */ new Map();
|
|
4337
|
+
}
|
|
4338
|
+
async get(scope, options) {
|
|
4339
|
+
const includeObsolete = options?.includeObsolete === true;
|
|
4340
|
+
const all = [...this.buckets.get(scope) || []].filter((fact) => includeObsolete || !fact.obsolete).sort((left, right) => new Date(right.lastUpdatedAt || 0).getTime() - new Date(left.lastUpdatedAt || 0).getTime());
|
|
4341
|
+
return typeof options?.limit === "number" ? all.slice(0, options.limit) : all;
|
|
4342
|
+
}
|
|
4343
|
+
async upsert(scope, facts) {
|
|
4344
|
+
const existing = [...this.buckets.get(scope) || []];
|
|
4345
|
+
for (const fact of facts) {
|
|
4346
|
+
const normalized = normalizeFact(scope, fact);
|
|
4347
|
+
const sameKey = existing.filter((entry) => entry.key === normalized.key && !entry.obsolete);
|
|
4348
|
+
for (const entry of sameKey) {
|
|
4349
|
+
if (entry.value !== normalized.value) {
|
|
4350
|
+
entry.obsolete = true;
|
|
4351
|
+
entry.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
const match = existing.find((entry) => entry.key === normalized.key && entry.value === normalized.value && !entry.obsolete);
|
|
4355
|
+
if (match) {
|
|
4356
|
+
match.confidence = Math.max(match.confidence, normalized.confidence);
|
|
4357
|
+
match.sourceTurn = normalized.sourceTurn;
|
|
4358
|
+
match.ttl = normalized.ttl;
|
|
4359
|
+
match.tags = normalized.tags;
|
|
4360
|
+
match.lastUpdatedAt = normalized.lastUpdatedAt;
|
|
4361
|
+
} else {
|
|
4362
|
+
existing.push(normalized);
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
this.buckets.set(scope, existing);
|
|
4366
|
+
}
|
|
4367
|
+
async markObsolete(scope, keys) {
|
|
4368
|
+
const bucket = this.buckets.get(scope) || [];
|
|
4369
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4370
|
+
for (const fact of bucket) {
|
|
4371
|
+
if (keys.includes(fact.key)) {
|
|
4372
|
+
fact.obsolete = true;
|
|
4373
|
+
fact.lastUpdatedAt = now;
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
this.buckets.set(scope, bucket);
|
|
4377
|
+
}
|
|
4378
|
+
async semanticSearch(scope, query, options) {
|
|
4379
|
+
const normalizedQuery = query.toLowerCase();
|
|
4380
|
+
const bucket = (this.buckets.get(scope) || []).filter((fact) => !fact.obsolete);
|
|
4381
|
+
const scored = bucket.map((fact) => {
|
|
4382
|
+
const haystack = `${fact.key} ${fact.value} ${(fact.tags || []).join(" ")}`.toLowerCase();
|
|
4383
|
+
const score = haystack.includes(normalizedQuery) ? 2 : normalizedQuery.split(/\s+/).filter(Boolean).reduce((acc, token) => acc + (haystack.includes(token) ? 1 : 0), 0);
|
|
4384
|
+
return { fact, score };
|
|
4385
|
+
}).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || right.fact.confidence - left.fact.confidence).map((entry) => entry.fact);
|
|
4386
|
+
return typeof options?.limit === "number" ? scored.slice(0, options.limit) : scored;
|
|
4387
|
+
}
|
|
4388
|
+
};
|
|
4389
|
+
function resolveMemoryStore(config) {
|
|
4390
|
+
if (config.memory.store) return config.memory.store;
|
|
4391
|
+
if (config.memory.provider === "inMemory") return new InMemoryMemoryStore();
|
|
4392
|
+
return void 0;
|
|
4393
|
+
}
|
|
4394
|
+
async function readMemoryFacts(store, config, query) {
|
|
4395
|
+
if (!store) return [];
|
|
4396
|
+
const scope = config.memory.scope;
|
|
4397
|
+
const limit = config.context.lastTurnsToKeep;
|
|
4398
|
+
const policy = config.memory.readPolicy;
|
|
4399
|
+
if (policy === "recent_only") {
|
|
4400
|
+
return store.get(scope, { limit });
|
|
4401
|
+
}
|
|
4402
|
+
if (policy === "semantic" && typeof store.semanticSearch === "function") {
|
|
4403
|
+
return store.semanticSearch(scope, query, { limit });
|
|
4404
|
+
}
|
|
4405
|
+
if (policy === "hybrid") {
|
|
4406
|
+
const recent = await store.get(scope, { limit: Math.max(3, Math.ceil(limit / 2)) });
|
|
4407
|
+
const semantic = typeof store.semanticSearch === "function" ? await store.semanticSearch(scope, query, { limit: Math.max(3, Math.ceil(limit / 2)) }) : [];
|
|
4408
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
4409
|
+
for (const fact of [...semantic, ...recent]) {
|
|
4410
|
+
deduped.set(`${fact.key}:${fact.value}`, fact);
|
|
4411
|
+
}
|
|
4412
|
+
return [...deduped.values()].slice(0, limit);
|
|
4413
|
+
}
|
|
4414
|
+
return store.get(scope, { limit });
|
|
4415
|
+
}
|
|
4416
|
+
async function writeSummaryFactsToMemory(store, config, summary, sourceTurn) {
|
|
4417
|
+
if (!store || !summary) return [];
|
|
4418
|
+
const facts = summary.stable_facts.map((fact) => ({
|
|
4419
|
+
key: fact.key,
|
|
4420
|
+
value: fact.value,
|
|
4421
|
+
sourceTurn,
|
|
4422
|
+
confidence: fact.confidence ?? 0.8,
|
|
4423
|
+
obsolete: false,
|
|
4424
|
+
tags: ["summary_fact"]
|
|
4425
|
+
}));
|
|
4426
|
+
if (summary.discarded_obsolete.length > 0) {
|
|
4427
|
+
await store.markObsolete(config.memory.scope, summary.discarded_obsolete);
|
|
4428
|
+
}
|
|
4429
|
+
if (facts.length > 0) {
|
|
4430
|
+
await store.upsert(config.memory.scope, facts);
|
|
4431
|
+
}
|
|
4432
|
+
return store.get(config.memory.scope, { limit: config.context.lastTurnsToKeep + summary.stable_facts.length });
|
|
4433
|
+
}
|
|
4434
|
+
function renderMemoryFacts(facts) {
|
|
4435
|
+
if (facts.length === 0) return "";
|
|
4436
|
+
return [
|
|
4437
|
+
"Memory facts:",
|
|
4438
|
+
...facts.map((fact) => `- ${fact.key}: ${fact.value} (confidence ${fact.confidence.toFixed(2)})`)
|
|
4439
|
+
].join("\n");
|
|
4440
|
+
}
|
|
4441
|
+
|
|
3046
4442
|
// src/smart/index.ts
|
|
3047
4443
|
function createSmartAgent(opts) {
|
|
3048
|
-
const
|
|
3049
|
-
const planningEnabled =
|
|
4444
|
+
const resolved = normalizeSmartAgentOptions(opts);
|
|
4445
|
+
const planningEnabled = resolved.planning.mode !== "off";
|
|
4446
|
+
const memoryStore = resolveMemoryStore(resolved);
|
|
4447
|
+
const runtimeOpts = {
|
|
4448
|
+
...opts,
|
|
4449
|
+
runtimeProfile: resolved.runtimeProfile,
|
|
4450
|
+
limits: resolved.limits,
|
|
4451
|
+
summarization: resolved.summarization,
|
|
4452
|
+
context: resolved.context,
|
|
4453
|
+
planning: resolved.planning,
|
|
4454
|
+
delegation: resolved.delegation,
|
|
4455
|
+
memory: { ...resolved.memory, store: memoryStore },
|
|
4456
|
+
toolResponses: resolved.toolResponses,
|
|
4457
|
+
watchdog: resolved.watchdog,
|
|
4458
|
+
useTodoList: planningEnabled
|
|
4459
|
+
};
|
|
4460
|
+
const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
|
|
3050
4461
|
const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
|
|
3051
4462
|
const mergedTools = [...opts.tools ?? [], ...contextTools];
|
|
3052
|
-
const base = createAgent({ ...
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
|
|
3060
|
-
const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
|
|
3061
|
-
const decideAfter = toolsDecisionFactory(opts, summarizationEnabled);
|
|
4463
|
+
const base = createAgent({ ...runtimeOpts, tools: mergedTools });
|
|
4464
|
+
base.__runtime.runtimeProfile = resolved.runtimeProfile;
|
|
4465
|
+
base.__runtime.smart = resolved;
|
|
4466
|
+
const summarizationEnabled = resolved.summarization.enable !== false;
|
|
4467
|
+
const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
|
|
4468
|
+
const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
|
|
4469
|
+
const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
|
|
3062
4470
|
const structuredOutputHint = opts.outputSchema ? [
|
|
3063
4471
|
"A structured output schema is active.",
|
|
3064
4472
|
"Do NOT output the final JSON directly as an assistant message.",
|
|
3065
4473
|
"When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
|
|
3066
4474
|
"Call it exactly once then STOP producing further assistant messages."
|
|
3067
4475
|
].join("\n") : "";
|
|
4476
|
+
const runtimeHint = [
|
|
4477
|
+
resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
|
|
4478
|
+
`Context policy: ${resolved.context.policy}.`,
|
|
4479
|
+
`Planning mode: ${resolved.planning.mode}. Replan policy: ${resolved.planning.replanPolicy}.`,
|
|
4480
|
+
`Delegation mode: ${resolved.delegation.mode}. Child context policy: ${resolved.delegation.childContextPolicy}.`,
|
|
4481
|
+
`Tool response policy: ${resolved.context.toolResponsePolicy}.`
|
|
4482
|
+
].join("\n");
|
|
4483
|
+
function latestUserPrompt(messages) {
|
|
4484
|
+
const latest = [...messages].reverse().find((message) => message.role === "user");
|
|
4485
|
+
if (!latest) return "";
|
|
4486
|
+
return typeof latest.content === "string" ? latest.content : Array.isArray(latest.content) ? latest.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join(" ") : "";
|
|
4487
|
+
}
|
|
4488
|
+
async function syncMemory(state) {
|
|
4489
|
+
const query = latestUserPrompt(state.messages || []);
|
|
4490
|
+
const memoryFacts = await readMemoryFacts(memoryStore, resolved, query);
|
|
4491
|
+
return { ...state, memoryFacts };
|
|
4492
|
+
}
|
|
4493
|
+
async function persistLatestSummary(state) {
|
|
4494
|
+
if (!memoryStore) return state;
|
|
4495
|
+
if (resolved.memory.writePolicy === "manual") return state;
|
|
4496
|
+
const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
|
|
4497
|
+
if (!latestSummary) return state;
|
|
4498
|
+
if (resolved.memory.writePolicy === "auto_important" && latestSummary.stable_facts.length === 0) {
|
|
4499
|
+
return state;
|
|
4500
|
+
}
|
|
4501
|
+
const memoryFacts = await writeSummaryFactsToMemory(memoryStore, resolved, latestSummary, Math.max(1, (state.messages || []).filter((message) => message.role === "user").length));
|
|
4502
|
+
return { ...state, memoryFacts };
|
|
4503
|
+
}
|
|
4504
|
+
function syncPlanState(state) {
|
|
4505
|
+
if (!Array.isArray(stateRef.todoList)) return state;
|
|
4506
|
+
return {
|
|
4507
|
+
...state,
|
|
4508
|
+
plan: {
|
|
4509
|
+
version: stateRef.planVersion || state.planVersion || 1,
|
|
4510
|
+
steps: stateRef.todoList,
|
|
4511
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4512
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4513
|
+
},
|
|
4514
|
+
planVersion: stateRef.planVersion || state.planVersion || 1
|
|
4515
|
+
};
|
|
4516
|
+
}
|
|
3068
4517
|
function systemMessage2() {
|
|
3069
4518
|
const sys = buildSystemPrompt(
|
|
3070
|
-
[opts.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
|
|
3071
|
-
|
|
3072
|
-
opts.name || "Agent"
|
|
4519
|
+
[opts.systemPrompt, runtimeHint, structuredOutputHint].filter(Boolean).join("\n"),
|
|
4520
|
+
planningEnabled,
|
|
4521
|
+
opts.name || "Agent",
|
|
4522
|
+
opts.todoListPrompt
|
|
3073
4523
|
);
|
|
3074
4524
|
return { role: "system", content: sys };
|
|
3075
4525
|
}
|
|
@@ -3077,38 +4527,81 @@ function createSmartAgent(opts) {
|
|
|
3077
4527
|
invoke: async (input, config) => {
|
|
3078
4528
|
stateRef.toolHistory = input.toolHistory;
|
|
3079
4529
|
stateRef.toolHistoryArchived = input.toolHistoryArchived;
|
|
4530
|
+
stateRef.todoList = input.plan?.steps;
|
|
4531
|
+
stateRef.planVersion = input.planVersion || input.plan?.version || 0;
|
|
4532
|
+
stateRef.adherenceScore = input.plan?.adherenceScore || 0;
|
|
3080
4533
|
const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
|
|
3081
4534
|
const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
|
|
3082
|
-
let state = { ...input, messages: seedMessages };
|
|
4535
|
+
let state = await syncMemory({ ...input, messages: seedMessages });
|
|
3083
4536
|
let lastResult = null;
|
|
3084
|
-
|
|
4537
|
+
let rawMessages = [...seedMessages];
|
|
4538
|
+
const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
|
|
3085
4539
|
const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
|
|
4540
|
+
let previousTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
|
|
3086
4541
|
for (let i = 0; i < iterationLimit; i++) {
|
|
4542
|
+
state = { ...state, messages: rawMessages };
|
|
3087
4543
|
const next = summarizationEnabled ? decideBefore(state) : "agent";
|
|
3088
4544
|
if (next === "contextSummarize" && summarizer) {
|
|
3089
4545
|
const delta = await summarizer(state);
|
|
3090
4546
|
if (!delta || Object.keys(delta).length === 0) ; else {
|
|
3091
|
-
state = { ...state, ...delta };
|
|
4547
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4548
|
+
rawMessages = [...state.messages || rawMessages];
|
|
3092
4549
|
continue;
|
|
3093
4550
|
}
|
|
3094
4551
|
}
|
|
3095
|
-
const
|
|
4552
|
+
const modelMessages = buildModelMessages(state, resolved);
|
|
4553
|
+
const res = await base.invoke({ ...state, messages: modelMessages }, config);
|
|
3096
4554
|
lastResult = res;
|
|
3097
4555
|
const currentSummaries = state.summaries;
|
|
4556
|
+
const currentSummaryRecords = state.summaryRecords;
|
|
4557
|
+
const currentPlan = state.plan;
|
|
4558
|
+
const appendedMessages = res.messages.slice(modelMessages.length);
|
|
4559
|
+
rawMessages = [...rawMessages, ...appendedMessages];
|
|
3098
4560
|
state = res.state || { ...state, messages: res.messages };
|
|
4561
|
+
state = { ...state, messages: rawMessages };
|
|
3099
4562
|
if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
|
|
3100
4563
|
state = { ...state, summaries: currentSummaries };
|
|
3101
4564
|
}
|
|
3102
|
-
if (state.
|
|
4565
|
+
if (currentSummaryRecords && currentSummaryRecords.length > 0 && (!state.summaryRecords || state.summaryRecords.length === 0)) {
|
|
4566
|
+
state = { ...state, summaryRecords: currentSummaryRecords };
|
|
4567
|
+
}
|
|
4568
|
+
if (currentPlan && !state.plan) {
|
|
4569
|
+
state = { ...state, plan: currentPlan };
|
|
4570
|
+
}
|
|
4571
|
+
state = syncPlanState(await syncMemory(state));
|
|
4572
|
+
stateRef.toolHistory = state.toolHistory;
|
|
4573
|
+
stateRef.toolHistoryArchived = state.toolHistoryArchived;
|
|
4574
|
+
const currentTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
|
|
4575
|
+
const tokenDrift = Math.max(0, currentTokenCount - previousTokenCount);
|
|
4576
|
+
const toolMessagesThisTurn = appendedMessages.filter((message) => message.role === "tool").length;
|
|
4577
|
+
const overToolingRate = appendedMessages.length === 0 ? 0 : Number((toolMessagesThisTurn / appendedMessages.length).toFixed(2));
|
|
4578
|
+
state = {
|
|
4579
|
+
...state,
|
|
4580
|
+
watchdog: {
|
|
4581
|
+
tokenDrift,
|
|
4582
|
+
contextRotScore: estimateContextRotScore(state),
|
|
4583
|
+
overToolingRate,
|
|
4584
|
+
compactions: state.watchdog?.compactions || 0,
|
|
4585
|
+
lastAction: appendedMessages.length > 0 ? "agent_turn" : state.watchdog?.lastAction
|
|
4586
|
+
}
|
|
4587
|
+
};
|
|
4588
|
+
previousTokenCount = currentTokenCount;
|
|
4589
|
+
if (resolved.watchdog.enabled && resolved.watchdog.autoCompaction) {
|
|
4590
|
+
const shouldCompact = tokenDrift >= resolved.watchdog.tokenDriftThreshold || (state.watchdog?.overToolingRate || 0) >= resolved.watchdog.overToolingSpikeThreshold || (state.watchdog?.contextRotScore || 0) >= resolved.watchdog.contextRotThreshold;
|
|
4591
|
+
if (shouldCompact) {
|
|
4592
|
+
state = { ...state, ctx: { ...state.ctx || {}, __needsSummarization: true } };
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
3103
4595
|
if (state.ctx?.__needsSummarization && summarizer) {
|
|
3104
4596
|
const ctx = { ...state.ctx || {} };
|
|
3105
4597
|
delete ctx.__needsSummarization;
|
|
3106
4598
|
state = { ...state, ctx };
|
|
3107
4599
|
const delta = await summarizer(state);
|
|
3108
4600
|
if (delta && Object.keys(delta).length > 0) {
|
|
3109
|
-
state = { ...state, ...delta };
|
|
4601
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4602
|
+
rawMessages = [...state.messages || rawMessages];
|
|
4603
|
+
continue;
|
|
3110
4604
|
}
|
|
3111
|
-
continue;
|
|
3112
4605
|
}
|
|
3113
4606
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
3114
4607
|
if (summarizationEnabled) {
|
|
@@ -3116,7 +4609,8 @@ function createSmartAgent(opts) {
|
|
|
3116
4609
|
if (after === "contextSummarize" && summarizer) {
|
|
3117
4610
|
const delta = await summarizer(state);
|
|
3118
4611
|
if (delta && Object.keys(delta).length > 0) {
|
|
3119
|
-
state = { ...state, ...delta };
|
|
4612
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4613
|
+
rawMessages = [...state.messages || rawMessages];
|
|
3120
4614
|
continue;
|
|
3121
4615
|
}
|
|
3122
4616
|
}
|
|
@@ -3129,10 +4623,12 @@ function createSmartAgent(opts) {
|
|
|
3129
4623
|
}
|
|
3130
4624
|
if (state.summaries && state.summaries.length > 0) {
|
|
3131
4625
|
if (lastResult.state) {
|
|
3132
|
-
lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
|
|
4626
|
+
lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries, summaryRecords: state.summaryRecords, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
|
|
3133
4627
|
} else {
|
|
3134
|
-
lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
|
|
4628
|
+
lastResult = { ...lastResult, state: { ...state, summaries: state.summaries, summaryRecords: state.summaryRecords, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
|
|
3135
4629
|
}
|
|
4630
|
+
} else if (lastResult.state) {
|
|
4631
|
+
lastResult = { ...lastResult, state: { ...lastResult.state, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
|
|
3136
4632
|
}
|
|
3137
4633
|
return lastResult;
|
|
3138
4634
|
},
|
|
@@ -3146,6 +4642,141 @@ function createSmartAgent(opts) {
|
|
|
3146
4642
|
return instance;
|
|
3147
4643
|
}
|
|
3148
4644
|
|
|
4645
|
+
// src/smart/eval.ts
|
|
4646
|
+
function normalizeProfileTarget(target) {
|
|
4647
|
+
if (typeof target === "string") {
|
|
4648
|
+
return {
|
|
4649
|
+
label: target,
|
|
4650
|
+
runtimeProfile: target,
|
|
4651
|
+
baseProfile: target === "custom" ? "balanced" : target
|
|
4652
|
+
};
|
|
4653
|
+
}
|
|
4654
|
+
return {
|
|
4655
|
+
label: target.label,
|
|
4656
|
+
runtimeProfile: target.runtimeProfile,
|
|
4657
|
+
baseProfile: target.baseProfile ?? (target.runtimeProfile === "custom" ? target.customProfile?.extends ?? "balanced" : target.runtimeProfile),
|
|
4658
|
+
customProfile: target.customProfile
|
|
4659
|
+
};
|
|
4660
|
+
}
|
|
4661
|
+
function normalizeText(value) {
|
|
4662
|
+
return (value || "").toLowerCase();
|
|
4663
|
+
}
|
|
4664
|
+
function ratio(numerator, denominator) {
|
|
4665
|
+
if (denominator <= 0) return 1;
|
|
4666
|
+
return Number((numerator / denominator).toFixed(3));
|
|
4667
|
+
}
|
|
4668
|
+
function sum(values) {
|
|
4669
|
+
return values.reduce((acc, value) => acc + value, 0);
|
|
4670
|
+
}
|
|
4671
|
+
function mean(values) {
|
|
4672
|
+
if (values.length === 0) return 0;
|
|
4673
|
+
return Number((sum(values) / values.length).toFixed(3));
|
|
4674
|
+
}
|
|
4675
|
+
function extractTotalTokens(usage) {
|
|
4676
|
+
if (!usage?.totals || typeof usage.totals !== "object") return 0;
|
|
4677
|
+
return Object.values(usage.totals).reduce((acc, entry) => acc + (Number(entry?.total) || 0), 0);
|
|
4678
|
+
}
|
|
4679
|
+
function caseResultFromRun(descriptor, testCase, content, events, state, latencyMs, totalTokens) {
|
|
4680
|
+
const normalizedContent = normalizeText(content);
|
|
4681
|
+
const matchedPhrases = (testCase.expectedPhrases || []).filter((phrase) => normalizedContent.includes(normalizeText(phrase)));
|
|
4682
|
+
const matchedFacts = (testCase.expectedFacts || []).filter((fact) => {
|
|
4683
|
+
const memoryMatch = state?.memoryFacts?.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value)));
|
|
4684
|
+
const summaryMatch = state?.summaryRecords?.some((record) => record.stable_facts.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value))));
|
|
4685
|
+
return Boolean(memoryMatch || summaryMatch || normalizedContent.includes(normalizeText(fact.value)));
|
|
4686
|
+
});
|
|
4687
|
+
const forbiddenMisses = (testCase.forbiddenPhrases || []).filter((phrase) => !normalizedContent.includes(normalizeText(phrase)));
|
|
4688
|
+
const toolCalls = events.filter((event) => event.type === "tool_call" && event.phase === "success");
|
|
4689
|
+
const expectedToolHits = (testCase.expectedToolNames || []).filter((toolName) => toolCalls.some((event) => event.name === toolName));
|
|
4690
|
+
const toolPrecision = ratio(expectedToolHits.length, Math.max(toolCalls.length, 1));
|
|
4691
|
+
const recallAccuracy = ratio(matchedPhrases.length + matchedFacts.length, (testCase.expectedPhrases?.length || 0) + (testCase.expectedFacts?.length || 0));
|
|
4692
|
+
const obsoleteDropAccuracy = ratio(forbiddenMisses.length, testCase.forbiddenPhrases?.length || 0);
|
|
4693
|
+
const trajectoryScore = ratio(expectedToolHits.length, testCase.expectedToolNames?.length || 0);
|
|
4694
|
+
const hadRecoverableError = events.some((event) => event.type === "tool_call" && event.phase === "error");
|
|
4695
|
+
const recoveryRate = hadRecoverableError ? Number((normalizedContent.length > 0 ? 1 : 0).toFixed(3)) : 1;
|
|
4696
|
+
const overToolingRate = toolCalls.length === 0 ? 0 : Number((Math.max(0, toolCalls.length - Math.max(testCase.expectedToolNames?.length || 0, 1)) / toolCalls.length).toFixed(3));
|
|
4697
|
+
const success = recallAccuracy >= 0.8 && obsoleteDropAccuracy >= 0.8;
|
|
4698
|
+
return {
|
|
4699
|
+
id: testCase.id,
|
|
4700
|
+
family: testCase.family,
|
|
4701
|
+
success,
|
|
4702
|
+
recallAccuracy,
|
|
4703
|
+
obsoleteDropAccuracy,
|
|
4704
|
+
trajectoryScore: Number(((trajectoryScore + toolPrecision) / 2).toFixed(3)),
|
|
4705
|
+
recoveryRate,
|
|
4706
|
+
overToolingRate,
|
|
4707
|
+
latencyMs,
|
|
4708
|
+
totalTokens,
|
|
4709
|
+
profile: descriptor.runtimeProfile,
|
|
4710
|
+
profileLabel: descriptor.label,
|
|
4711
|
+
baseProfile: descriptor.baseProfile,
|
|
4712
|
+
notes: success ? [] : [
|
|
4713
|
+
recallAccuracy < 0.8 ? "Recall below threshold." : "",
|
|
4714
|
+
obsoleteDropAccuracy < 0.8 ? "Obsolete filtering below threshold." : "",
|
|
4715
|
+
trajectoryScore < 0.5 ? "Tool trajectory diverged from expectation." : ""
|
|
4716
|
+
].filter(Boolean)
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4719
|
+
function aggregateMetrics(caseResults) {
|
|
4720
|
+
const taskSuccess = ratio(caseResults.filter((result) => result.success).length, caseResults.length);
|
|
4721
|
+
const recallAccuracy = mean(caseResults.map((result) => result.recallAccuracy));
|
|
4722
|
+
const obsoleteDropAccuracy = mean(caseResults.map((result) => result.obsoleteDropAccuracy));
|
|
4723
|
+
const trajectoryScore = mean(caseResults.map((result) => result.trajectoryScore));
|
|
4724
|
+
const recoveryRate = mean(caseResults.map((result) => result.recoveryRate));
|
|
4725
|
+
const overToolingRate = mean(caseResults.map((result) => result.overToolingRate));
|
|
4726
|
+
const latencyMs = mean(caseResults.map((result) => result.latencyMs));
|
|
4727
|
+
const totalTokens = sum(caseResults.map((result) => result.totalTokens || 0));
|
|
4728
|
+
const toolPrecision = trajectoryScore;
|
|
4729
|
+
const costScore = totalTokens <= 0 ? 1 : Number((1 / (1 + totalTokens / 1e4)).toFixed(3));
|
|
4730
|
+
const score = Number((0.45 * taskSuccess + 0.2 * trajectoryScore + 0.15 * toolPrecision + 0.1 * recoveryRate + 0.1 * costScore).toFixed(3));
|
|
4731
|
+
return {
|
|
4732
|
+
taskSuccess,
|
|
4733
|
+
recallAccuracy,
|
|
4734
|
+
obsoleteDropAccuracy,
|
|
4735
|
+
trajectoryScore,
|
|
4736
|
+
recoveryRate,
|
|
4737
|
+
overToolingRate,
|
|
4738
|
+
latencyMs,
|
|
4739
|
+
totalTokens,
|
|
4740
|
+
score
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
async function runSmartAgentEvalHarness(params) {
|
|
4744
|
+
const results = [];
|
|
4745
|
+
for (const profileTarget of params.profiles) {
|
|
4746
|
+
const descriptor = normalizeProfileTarget(profileTarget);
|
|
4747
|
+
const caseResults = [];
|
|
4748
|
+
for (const testCase of params.cases) {
|
|
4749
|
+
const agent = params.createAgent(descriptor.runtimeProfile, descriptor);
|
|
4750
|
+
const events = [];
|
|
4751
|
+
const startedAt = Date.now();
|
|
4752
|
+
const result = await agent.invoke({
|
|
4753
|
+
messages: [{ role: "user", content: testCase.prompt }]
|
|
4754
|
+
}, {
|
|
4755
|
+
onEvent: (event) => {
|
|
4756
|
+
events.push(event);
|
|
4757
|
+
}
|
|
4758
|
+
});
|
|
4759
|
+
caseResults.push(caseResultFromRun(
|
|
4760
|
+
descriptor,
|
|
4761
|
+
testCase,
|
|
4762
|
+
result.content,
|
|
4763
|
+
events,
|
|
4764
|
+
result.state,
|
|
4765
|
+
Date.now() - startedAt,
|
|
4766
|
+
extractTotalTokens(result.metadata?.usage)
|
|
4767
|
+
));
|
|
4768
|
+
}
|
|
4769
|
+
results.push({
|
|
4770
|
+
profile: descriptor.runtimeProfile,
|
|
4771
|
+
profileLabel: descriptor.label,
|
|
4772
|
+
baseProfile: descriptor.baseProfile,
|
|
4773
|
+
metrics: aggregateMetrics(caseResults),
|
|
4774
|
+
cases: caseResults
|
|
4775
|
+
});
|
|
4776
|
+
}
|
|
4777
|
+
return results;
|
|
4778
|
+
}
|
|
4779
|
+
|
|
3149
4780
|
// src/utils/content.ts
|
|
3150
4781
|
function contentToString(content) {
|
|
3151
4782
|
try {
|
|
@@ -3423,7 +5054,9 @@ function createCodeGuardrail(options) {
|
|
|
3423
5054
|
});
|
|
3424
5055
|
}
|
|
3425
5056
|
|
|
5057
|
+
exports.DEFAULT_PROFILE_CONFIGS = DEFAULT_PROFILE_CONFIGS;
|
|
3426
5058
|
exports.GuardrailPhase = GuardrailPhase;
|
|
5059
|
+
exports.InMemoryMemoryStore = InMemoryMemoryStore;
|
|
3427
5060
|
exports.agentVerdictRule = agentVerdictRule;
|
|
3428
5061
|
exports.applyTokenLimits = applyTokenLimits;
|
|
3429
5062
|
exports.buildSystemPrompt = buildSystemPrompt;
|
|
@@ -3451,13 +5084,24 @@ exports.evaluateGuardrails = evaluateGuardrails;
|
|
|
3451
5084
|
exports.fileSink = fileSink;
|
|
3452
5085
|
exports.fromLangchainModel = fromLangchainModel;
|
|
3453
5086
|
exports.fromLangchainTools = fromLangchainTools;
|
|
5087
|
+
exports.generateSpanId = generateSpanId;
|
|
5088
|
+
exports.generateTraceId = generateTraceId;
|
|
5089
|
+
exports.getResolvedSmartConfig = getResolvedSmartConfig;
|
|
3454
5090
|
exports.httpSink = httpSink;
|
|
3455
5091
|
exports.isSmartModel = isSmartModel;
|
|
3456
5092
|
exports.jsonSchemaRule = jsonSchemaRule;
|
|
5093
|
+
exports.normalizeSmartAgentOptions = normalizeSmartAgentOptions;
|
|
5094
|
+
exports.otlpSink = otlpSink;
|
|
5095
|
+
exports.readMemoryFacts = readMemoryFacts;
|
|
3457
5096
|
exports.regexRule = regexRule;
|
|
5097
|
+
exports.renderMemoryFacts = renderMemoryFacts;
|
|
5098
|
+
exports.resolveMemoryStore = resolveMemoryStore;
|
|
3458
5099
|
exports.resolveToolApprovalState = resolveToolApprovalState;
|
|
3459
5100
|
exports.restoreSnapshot = restoreSnapshot;
|
|
5101
|
+
exports.runSmartAgentEvalHarness = runSmartAgentEvalHarness;
|
|
3460
5102
|
exports.startStreamingSession = startStreamingSession;
|
|
5103
|
+
exports.traceSessionToOtlp = traceSessionToOtlp;
|
|
3461
5104
|
exports.withTools = withTools;
|
|
5105
|
+
exports.writeSummaryFactsToMemory = writeSummaryFactsToMemory;
|
|
3462
5106
|
//# sourceMappingURL=index.cjs.map
|
|
3463
5107
|
//# sourceMappingURL=index.cjs.map
|