@cognipeer/agent-sdk 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -9
- package/dist/index.cjs +1930 -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 +1918 -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,523 @@ 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
|
+
return JSON.stringify(value);
|
|
2153
|
+
} catch {
|
|
2154
|
+
return String(value);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
function summarizeObject(value) {
|
|
2158
|
+
if (value == null) return "null";
|
|
2159
|
+
if (typeof value === "string") {
|
|
2160
|
+
return value.length > 240 ? `${value.slice(0, 240)}...` : value;
|
|
2161
|
+
}
|
|
2162
|
+
if (Array.isArray(value)) {
|
|
2163
|
+
const preview = value.slice(0, 3).map((entry) => safeStringify(entry).slice(0, 120)).join(" | ");
|
|
2164
|
+
return `array(length=${value.length}) ${preview}`.trim();
|
|
2165
|
+
}
|
|
2166
|
+
if (typeof value === "object") {
|
|
2167
|
+
const keys = Object.keys(value);
|
|
2168
|
+
const preview = keys.slice(0, 6).map((key) => `${key}=${safeStringify(value[key]).slice(0, 80)}`).join("; ");
|
|
2169
|
+
return `object(keys=${keys.length}) ${preview}`.trim();
|
|
2170
|
+
}
|
|
2171
|
+
return safeStringify(value);
|
|
2172
|
+
}
|
|
2173
|
+
function classifyToolResponse(toolName, output, config) {
|
|
2174
|
+
const serialized = safeStringify(output);
|
|
2175
|
+
const tokenCount = countApproxTokens(serialized);
|
|
2176
|
+
const criticalTools = new Set(config.toolResponses.criticalTools);
|
|
2177
|
+
if (criticalTools.has(toolName)) return "critical";
|
|
2178
|
+
if (tokenCount >= config.toolResponses.maxToolResponseTokens || serialized.length >= config.toolResponses.maxToolResponseChars) {
|
|
2179
|
+
return "verbose";
|
|
2180
|
+
}
|
|
2181
|
+
if (serialized === "ok" || serialized === "true" || serialized === "null") {
|
|
2182
|
+
return "redundant";
|
|
2183
|
+
}
|
|
2184
|
+
return "informative";
|
|
2185
|
+
}
|
|
2186
|
+
function resolveToolResponsePolicy(toolName, output, config) {
|
|
2187
|
+
const serialized = safeStringify(output);
|
|
2188
|
+
const tokenCount = countApproxTokens(serialized);
|
|
2189
|
+
const classification = classifyToolResponse(toolName, output, config);
|
|
2190
|
+
const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
|
|
2191
|
+
let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
|
|
2192
|
+
if (classification === "critical") {
|
|
2193
|
+
retentionPolicy = byTool || "keep_structured";
|
|
2194
|
+
} else if (classification === "verbose") {
|
|
2195
|
+
retentionPolicy = byTool || config.toolResponses.largeResponsePolicy;
|
|
2196
|
+
} else if (classification === "redundant") {
|
|
2197
|
+
retentionPolicy = byTool || "drop";
|
|
2198
|
+
}
|
|
2199
|
+
const summary = summarizeObject(output);
|
|
2200
|
+
if (retentionPolicy === "keep_full") {
|
|
2201
|
+
return { classification, retentionPolicy, content: serialized, rawOutput: output, summary, tokenCount };
|
|
2202
|
+
}
|
|
2203
|
+
if (retentionPolicy === "keep_structured") {
|
|
2204
|
+
return { classification, retentionPolicy, content: summary, rawOutput: output, summary, tokenCount };
|
|
2205
|
+
}
|
|
2206
|
+
if (retentionPolicy === "summarize_archive") {
|
|
2207
|
+
return {
|
|
2208
|
+
classification,
|
|
2209
|
+
retentionPolicy,
|
|
2210
|
+
content: `ARCHIVED_TOOL_RESPONSE: ${summary}`,
|
|
2211
|
+
rawOutput: output,
|
|
2212
|
+
summary,
|
|
2213
|
+
tokenCount
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
return {
|
|
2217
|
+
classification,
|
|
2218
|
+
retentionPolicy,
|
|
2219
|
+
content: `DROPPED_TOOL_RESPONSE: ${summary}`,
|
|
2220
|
+
rawOutput: output,
|
|
2221
|
+
summary,
|
|
2222
|
+
tokenCount
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
function validateToolArgs(tool, args) {
|
|
2226
|
+
const schema = tool.schema;
|
|
2227
|
+
if (!schema || typeof schema.parse !== "function") {
|
|
2228
|
+
return { ok: true, value: args };
|
|
2229
|
+
}
|
|
2230
|
+
try {
|
|
2231
|
+
return { ok: true, value: schema.parse(args) };
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
if (error instanceof zod.ZodError) {
|
|
2234
|
+
const message = error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
|
|
2235
|
+
return { ok: false, message };
|
|
2236
|
+
}
|
|
2237
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
// src/nodes/tools.ts
|
|
2242
|
+
function normalizeMaxExecutionsPerRun(value) {
|
|
2243
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
2244
|
+
const parsed = Number(value);
|
|
2245
|
+
if (!Number.isFinite(parsed)) return null;
|
|
2246
|
+
const normalized = Math.trunc(parsed);
|
|
2247
|
+
return normalized >= 0 ? normalized : null;
|
|
2248
|
+
}
|
|
2249
|
+
function countSuccessfulToolExecutions(toolHistory, toolName) {
|
|
2250
|
+
if (!Array.isArray(toolHistory)) return 0;
|
|
2251
|
+
return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
|
|
2252
|
+
}
|
|
1472
2253
|
function createToolsNode(initialTools, opts) {
|
|
1473
|
-
const baseToolByName = /* @__PURE__ */ new Map();
|
|
1474
|
-
for (const t of initialTools) baseToolByName.set(t.name, t);
|
|
1475
2254
|
return async (state) => {
|
|
1476
2255
|
const runtime = state.agent || {
|
|
1477
2256
|
name: opts?.name,
|
|
@@ -1479,15 +2258,17 @@ function createToolsNode(initialTools, opts) {
|
|
|
1479
2258
|
tools: initialTools,
|
|
1480
2259
|
limits: opts?.limits,
|
|
1481
2260
|
systemPrompt: opts?.systemPrompt,
|
|
2261
|
+
todoListPrompt: opts?.todoListPrompt,
|
|
1482
2262
|
useTodoList: opts?.useTodoList,
|
|
1483
2263
|
outputSchema: opts?.outputSchema
|
|
1484
2264
|
};
|
|
2265
|
+
const resolved = getResolvedSmartConfig(opts || {}, runtime);
|
|
1485
2266
|
const activeTools = runtime.tools;
|
|
1486
2267
|
const toolByName = /* @__PURE__ */ new Map();
|
|
1487
|
-
for (const
|
|
2268
|
+
for (const tool of activeTools) toolByName.set(tool.name, tool);
|
|
1488
2269
|
const limits = {
|
|
1489
|
-
maxToolCalls: runtime.limits?.maxToolCalls ?? 10,
|
|
1490
|
-
maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? 1)
|
|
2270
|
+
maxToolCalls: runtime.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10,
|
|
2271
|
+
maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? resolved.limits.maxParallelTools ?? 1)
|
|
1491
2272
|
};
|
|
1492
2273
|
const appended = [];
|
|
1493
2274
|
const onEvent = state.ctx?.__onEvent;
|
|
@@ -1498,20 +2279,19 @@ function createToolsNode(initialTools, opts) {
|
|
|
1498
2279
|
const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
|
|
1499
2280
|
const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
|
|
1500
2281
|
let awaitingApproval = false;
|
|
1501
|
-
for (const
|
|
1502
|
-
const
|
|
1503
|
-
if (
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
2282
|
+
for (const tool of toolByName.values()) {
|
|
2283
|
+
const anyTool = tool;
|
|
2284
|
+
if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
|
|
2285
|
+
anyTool._stateRef.toolHistory = state.toolHistory;
|
|
2286
|
+
anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
|
|
2287
|
+
anyTool._stateRef.__onEvent = onEvent;
|
|
1507
2288
|
}
|
|
1508
2289
|
}
|
|
1509
2290
|
const last = state.messages[state.messages.length - 1];
|
|
1510
2291
|
let toolCount = state.toolCallCount || 0;
|
|
1511
|
-
const toolCalls = Array.isArray(
|
|
1512
|
-
last?.tool_calls
|
|
1513
|
-
) ? last.tool_calls : [];
|
|
2292
|
+
const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
|
|
1514
2293
|
const toolHistory = state.toolHistory || [];
|
|
2294
|
+
const toolHistoryArchived = state.toolHistoryArchived || [];
|
|
1515
2295
|
const remaining = Math.max(0, limits.maxToolCalls - toolCount);
|
|
1516
2296
|
const planned = toolCalls.slice(0, remaining);
|
|
1517
2297
|
const skipped = toolCalls.slice(remaining);
|
|
@@ -1520,6 +2300,17 @@ function createToolsNode(initialTools, opts) {
|
|
|
1520
2300
|
if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
|
|
1521
2301
|
return { cancelled: false, reason: void 0 };
|
|
1522
2302
|
};
|
|
2303
|
+
const markToolFailure = (message, toolName, toolCallId) => {
|
|
2304
|
+
const ctx = { ...state.ctx || {} };
|
|
2305
|
+
ctx.__lastToolError = message;
|
|
2306
|
+
if (toolCallId) {
|
|
2307
|
+
ctx.__toolSchemaError = { toolName, toolCallId, message };
|
|
2308
|
+
}
|
|
2309
|
+
if (resolved.watchdog.autoReplanOnFailure) {
|
|
2310
|
+
ctx.__planNeedsReplan = true;
|
|
2311
|
+
}
|
|
2312
|
+
state.ctx = ctx;
|
|
2313
|
+
};
|
|
1523
2314
|
const runOne = async (tc) => {
|
|
1524
2315
|
const cancelState = isCancelled();
|
|
1525
2316
|
if (cancelState.cancelled) {
|
|
@@ -1529,15 +2320,10 @@ function createToolsNode(initialTools, opts) {
|
|
|
1529
2320
|
onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
|
|
1530
2321
|
return { status: "error" };
|
|
1531
2322
|
}
|
|
1532
|
-
const
|
|
1533
|
-
if (!
|
|
2323
|
+
const tool = toolByName.get(tc.name);
|
|
2324
|
+
if (!tool) {
|
|
2325
|
+
appended.push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1534
2326
|
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
2327
|
toolCount += 1;
|
|
1542
2328
|
return { status: "error" };
|
|
1543
2329
|
}
|
|
@@ -1545,22 +2331,67 @@ function createToolsNode(initialTools, opts) {
|
|
|
1545
2331
|
if (typeof args === "string") {
|
|
1546
2332
|
try {
|
|
1547
2333
|
args = JSON.parse(args);
|
|
1548
|
-
} catch
|
|
2334
|
+
} catch {
|
|
1549
2335
|
}
|
|
1550
2336
|
}
|
|
1551
2337
|
const toolCallId = tc.id || `${tc.name}_${toolCount + 1}`;
|
|
2338
|
+
const validatedArgs = validateToolArgs(tool, args);
|
|
2339
|
+
if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
|
|
2340
|
+
const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
|
|
2341
|
+
appended.push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
|
|
2342
|
+
markToolFailure(errorMessage, tc.name, toolCallId);
|
|
2343
|
+
onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
|
|
2344
|
+
toolCount += 1;
|
|
2345
|
+
return { status: "error" };
|
|
2346
|
+
}
|
|
2347
|
+
if (validatedArgs.ok) {
|
|
2348
|
+
args = validatedArgs.value;
|
|
2349
|
+
}
|
|
2350
|
+
const toolName = tool.name || tc.name;
|
|
2351
|
+
const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
|
|
2352
|
+
if (maxExecutionsPerRun !== null) {
|
|
2353
|
+
const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
|
|
2354
|
+
if (currentExecutions >= maxExecutionsPerRun) {
|
|
2355
|
+
const limitMessage = `Skipped tool due to per-tool execution limit: ${toolName} (${maxExecutionsPerRun}/run)`;
|
|
2356
|
+
appended.push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
|
|
2357
|
+
onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args });
|
|
2358
|
+
recordTraceEvent(traceSession, {
|
|
2359
|
+
type: "tool_call",
|
|
2360
|
+
label: `Tool Skipped - ${toolName}`,
|
|
2361
|
+
actor: { scope: "tool", name: toolName, role: "tool" },
|
|
2362
|
+
status: "skipped",
|
|
2363
|
+
toolExecutionId: tc.id,
|
|
2364
|
+
messageList: [
|
|
2365
|
+
{
|
|
2366
|
+
role: "assistant",
|
|
2367
|
+
name: toolName,
|
|
2368
|
+
content: limitMessage,
|
|
2369
|
+
tool_calls: [
|
|
2370
|
+
{
|
|
2371
|
+
id: tc.id,
|
|
2372
|
+
type: "function",
|
|
2373
|
+
function: { name: toolName, arguments: sanitizeTracePayload(args) }
|
|
2374
|
+
}
|
|
2375
|
+
]
|
|
2376
|
+
}
|
|
2377
|
+
]
|
|
2378
|
+
});
|
|
2379
|
+
toolCount += 1;
|
|
2380
|
+
return { status: "error" };
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
1552
2383
|
let approvalEntry = pendingByCallId.get(toolCallId);
|
|
1553
|
-
const needsApproval = Boolean(
|
|
2384
|
+
const needsApproval = Boolean(tool.needsApproval);
|
|
1554
2385
|
if (needsApproval) {
|
|
1555
2386
|
if (!approvalEntry) {
|
|
1556
2387
|
approvalEntry = {
|
|
1557
2388
|
id: nanoid.nanoid(),
|
|
1558
2389
|
toolCallId,
|
|
1559
|
-
toolName:
|
|
2390
|
+
toolName: tool.name || tc.name,
|
|
1560
2391
|
args,
|
|
1561
2392
|
status: "pending",
|
|
1562
2393
|
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1563
|
-
metadata:
|
|
2394
|
+
metadata: tool.approvalPrompt || tool.approvalDefaults ? { prompt: tool.approvalPrompt, defaults: tool.approvalDefaults } : void 0
|
|
1564
2395
|
};
|
|
1565
2396
|
pendingApprovals.push(approvalEntry);
|
|
1566
2397
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
@@ -1582,71 +2413,97 @@ function createToolsNode(initialTools, opts) {
|
|
|
1582
2413
|
}
|
|
1583
2414
|
if (approvalEntry.status === "rejected") {
|
|
1584
2415
|
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 });
|
|
2416
|
+
appended.push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
|
|
1592
2417
|
approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
|
|
1593
2418
|
approvalEntry.status = "executed";
|
|
1594
2419
|
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
2420
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
2421
|
+
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" });
|
|
2422
|
+
onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
|
|
1597
2423
|
toolCount += 1;
|
|
1598
2424
|
return { status: "rejected", approval: approvalEntry };
|
|
1599
2425
|
}
|
|
1600
|
-
if (approvalEntry.status === "approved") {
|
|
1601
|
-
|
|
1602
|
-
args = approvalEntry.approvedArgs;
|
|
1603
|
-
}
|
|
2426
|
+
if (approvalEntry.status === "approved" && approvalEntry.approvedArgs !== void 0) {
|
|
2427
|
+
args = approvalEntry.approvedArgs;
|
|
1604
2428
|
onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
|
|
1605
2429
|
}
|
|
1606
2430
|
}
|
|
1607
2431
|
const start = Date.now();
|
|
2432
|
+
const sanitizedArgs = sanitizeTracePayload(args);
|
|
2433
|
+
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1608
2434
|
try {
|
|
1609
|
-
onEvent?.({ type: "tool_call", phase: "start", name:
|
|
2435
|
+
onEvent?.({ type: "tool_call", phase: "start", name: toolName, id: tc.id, args });
|
|
1610
2436
|
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;
|
|
2437
|
+
const anyTool = tool;
|
|
1615
2438
|
const callOptions = { cancellationToken, signal: abortSignal };
|
|
2439
|
+
let output;
|
|
1616
2440
|
if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
|
|
1617
2441
|
else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
|
|
1618
2442
|
else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
|
|
1619
2443
|
else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
|
|
1620
2444
|
else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
|
|
1621
2445
|
else throw new Error("Tool is not invokable");
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
toolHistory.push({ executionId
|
|
2446
|
+
const durationMs = Date.now() - start;
|
|
2447
|
+
const executionId = nanoid.nanoid();
|
|
2448
|
+
if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
|
|
2449
|
+
toolHistory.push({ executionId, toolName, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "handoff" });
|
|
1626
2450
|
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;
|
|
2451
|
+
state.agent = output.__handoff.runtime;
|
|
2452
|
+
onEvent?.({ type: "handoff", from: runtime.name, to: state.agent?.name, toolName });
|
|
2453
|
+
onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: "handoff", durationMs });
|
|
1631
2454
|
if (needsApproval && approvalEntry) {
|
|
1632
2455
|
approvalEntry.status = "executed";
|
|
1633
2456
|
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1634
|
-
approvalEntry.executionId =
|
|
2457
|
+
approvalEntry.executionId = executionId;
|
|
1635
2458
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
1636
2459
|
}
|
|
2460
|
+
toolCount += 1;
|
|
1637
2461
|
return { status: "success", approval: approvalEntry };
|
|
1638
2462
|
}
|
|
1639
|
-
const content = typeof output === "string" ? output : JSON.stringify(output);
|
|
1640
2463
|
if (output && typeof output === "object" && output.__finalStructuredOutput) {
|
|
1641
2464
|
if (!state.ctx) state.ctx = {};
|
|
1642
2465
|
state.ctx.__structuredOutputParsed = output.data;
|
|
1643
2466
|
state.ctx.__finalizedDueToStructuredOutput = true;
|
|
1644
2467
|
}
|
|
1645
|
-
const
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
2468
|
+
const responsePolicy = resolveToolResponsePolicy(toolName, output, resolved);
|
|
2469
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2470
|
+
const storedOutput = responsePolicy.retentionPolicy === "keep_full" ? output : responsePolicy.summary;
|
|
2471
|
+
toolHistory.push({
|
|
2472
|
+
executionId,
|
|
2473
|
+
toolName,
|
|
2474
|
+
args,
|
|
2475
|
+
output: storedOutput,
|
|
2476
|
+
rawOutput: output,
|
|
2477
|
+
timestamp,
|
|
2478
|
+
tool_call_id: tc.id,
|
|
2479
|
+
summarized: responsePolicy.retentionPolicy !== "keep_full",
|
|
2480
|
+
originalTokenCount: responsePolicy.tokenCount,
|
|
2481
|
+
classification: responsePolicy.classification,
|
|
2482
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2483
|
+
summary: responsePolicy.summary,
|
|
2484
|
+
archiveId: responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop" ? executionId : void 0,
|
|
2485
|
+
status: "success"
|
|
2486
|
+
});
|
|
2487
|
+
if (resolved.context.archiveLargeToolResponses && (responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop")) {
|
|
2488
|
+
toolHistoryArchived.push({
|
|
2489
|
+
executionId,
|
|
2490
|
+
toolName,
|
|
2491
|
+
args,
|
|
2492
|
+
output,
|
|
2493
|
+
rawOutput: output,
|
|
2494
|
+
timestamp,
|
|
2495
|
+
tool_call_id: tc.id,
|
|
2496
|
+
summarized: true,
|
|
2497
|
+
originalTokenCount: responsePolicy.tokenCount,
|
|
2498
|
+
classification: responsePolicy.classification,
|
|
2499
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2500
|
+
summary: responsePolicy.summary,
|
|
2501
|
+
archiveId: executionId,
|
|
2502
|
+
status: "success"
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
2506
|
+
onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
|
|
1650
2507
|
onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
|
|
1651
2508
|
if (needsApproval && approvalEntry) {
|
|
1652
2509
|
approvalEntry.status = "executed";
|
|
@@ -1656,7 +2513,6 @@ function createToolsNode(initialTools, opts) {
|
|
|
1656
2513
|
}
|
|
1657
2514
|
const sanitizedOutput = sanitizeTracePayload(output);
|
|
1658
2515
|
const outputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedOutput) : void 0;
|
|
1659
|
-
const toolName = t.name || tc.name;
|
|
1660
2516
|
const messageList = [
|
|
1661
2517
|
{
|
|
1662
2518
|
role: "assistant",
|
|
@@ -1676,7 +2532,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1676
2532
|
{
|
|
1677
2533
|
role: "tool",
|
|
1678
2534
|
name: toolName,
|
|
1679
|
-
content:
|
|
2535
|
+
content: responsePolicy.content
|
|
1680
2536
|
}
|
|
1681
2537
|
];
|
|
1682
2538
|
recordTraceEvent(traceSession, {
|
|
@@ -1687,43 +2543,24 @@ function createToolsNode(initialTools, opts) {
|
|
|
1687
2543
|
requestBytes: inputBytes,
|
|
1688
2544
|
responseBytes: outputBytes,
|
|
1689
2545
|
toolExecutionId: executionId,
|
|
1690
|
-
messageList
|
|
2546
|
+
messageList,
|
|
2547
|
+
debug: {
|
|
2548
|
+
classification: responsePolicy.classification,
|
|
2549
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2550
|
+
originalTokenCount: responsePolicy.tokenCount
|
|
2551
|
+
}
|
|
1691
2552
|
});
|
|
1692
2553
|
toolCount += 1;
|
|
1693
2554
|
return { status: "success", approval: approvalEntry };
|
|
1694
|
-
} catch (
|
|
2555
|
+
} catch (error) {
|
|
1695
2556
|
const durationMs = Date.now() - start;
|
|
1696
2557
|
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
|
-
];
|
|
2558
|
+
const message = error?.message || String(error);
|
|
2559
|
+
toolHistory.push({ executionId, toolName, args, output: `Error executing tool: ${message}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "error" });
|
|
2560
|
+
appended.push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
2561
|
+
markToolFailure(message, toolName, toolCallId);
|
|
2562
|
+
onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
|
|
2563
|
+
onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
|
|
1727
2564
|
recordTraceEvent(traceSession, {
|
|
1728
2565
|
type: "tool_call",
|
|
1729
2566
|
label: `Tool Error - ${toolName}`,
|
|
@@ -1732,8 +2569,26 @@ function createToolsNode(initialTools, opts) {
|
|
|
1732
2569
|
durationMs,
|
|
1733
2570
|
requestBytes: inputBytes,
|
|
1734
2571
|
toolExecutionId: executionId,
|
|
1735
|
-
error: { message
|
|
1736
|
-
messageList
|
|
2572
|
+
error: { message, stack: error?.stack },
|
|
2573
|
+
messageList: [
|
|
2574
|
+
{
|
|
2575
|
+
role: "assistant",
|
|
2576
|
+
name: toolName,
|
|
2577
|
+
content: "",
|
|
2578
|
+
tool_calls: [
|
|
2579
|
+
{
|
|
2580
|
+
id: tc.id || executionId,
|
|
2581
|
+
type: "function",
|
|
2582
|
+
function: { name: toolName, arguments: sanitizedArgs }
|
|
2583
|
+
}
|
|
2584
|
+
]
|
|
2585
|
+
},
|
|
2586
|
+
{
|
|
2587
|
+
role: "tool",
|
|
2588
|
+
name: toolName,
|
|
2589
|
+
content: `Error executing tool: ${message}`
|
|
2590
|
+
}
|
|
2591
|
+
]
|
|
1737
2592
|
});
|
|
1738
2593
|
toolCount += 1;
|
|
1739
2594
|
return { status: "error", approval: approvalEntry };
|
|
@@ -1750,7 +2605,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1750
2605
|
break;
|
|
1751
2606
|
}
|
|
1752
2607
|
const result = await runOne(tc);
|
|
1753
|
-
if (result
|
|
2608
|
+
if (result.status === "awaiting_approval") {
|
|
1754
2609
|
awaitingApproval = true;
|
|
1755
2610
|
break;
|
|
1756
2611
|
}
|
|
@@ -1758,37 +2613,28 @@ function createToolsNode(initialTools, opts) {
|
|
|
1758
2613
|
for (const tc of skipped) {
|
|
1759
2614
|
onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
|
|
1760
2615
|
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
2616
|
recordTraceEvent(traceSession, {
|
|
1779
2617
|
type: "tool_call",
|
|
1780
2618
|
label: `Tool Skipped - ${tc.name}`,
|
|
1781
2619
|
actor: { scope: "tool", name: tc.name, role: "tool" },
|
|
1782
2620
|
status: "skipped",
|
|
1783
2621
|
toolExecutionId: tc.id,
|
|
1784
|
-
messageList
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
2622
|
+
messageList: [
|
|
2623
|
+
{
|
|
2624
|
+
role: "assistant",
|
|
2625
|
+
name: tc.name,
|
|
2626
|
+
content: `Skipped tool due to tool-call limit: ${tc.name}`,
|
|
2627
|
+
tool_calls: [
|
|
2628
|
+
{
|
|
2629
|
+
id: tc.id,
|
|
2630
|
+
type: "function",
|
|
2631
|
+
function: { name: tc.name, arguments: sanitizedArgs }
|
|
2632
|
+
}
|
|
2633
|
+
]
|
|
2634
|
+
}
|
|
2635
|
+
]
|
|
1791
2636
|
});
|
|
2637
|
+
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
2638
|
toolCount += 1;
|
|
1793
2639
|
}
|
|
1794
2640
|
if (!awaitingApproval && state.ctx?.__awaitingApproval) {
|
|
@@ -1803,6 +2649,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1803
2649
|
messages: [...state.messages, ...appended],
|
|
1804
2650
|
toolCallCount: toolCount,
|
|
1805
2651
|
toolHistory,
|
|
2652
|
+
toolHistoryArchived,
|
|
1806
2653
|
agent: state.agent,
|
|
1807
2654
|
pendingApprovals
|
|
1808
2655
|
};
|
|
@@ -2027,68 +2874,20 @@ function resolveToolApprovalState(state, resolution) {
|
|
|
2027
2874
|
};
|
|
2028
2875
|
}
|
|
2029
2876
|
|
|
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
|
-
}
|
|
2877
|
+
// src/agent.ts
|
|
2878
|
+
function isSyntheticSummaryMessage(message) {
|
|
2879
|
+
if (!message) return false;
|
|
2880
|
+
if (message.role === "tool" && message.name === "summarize_context") {
|
|
2881
|
+
return true;
|
|
2072
2882
|
}
|
|
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
|
-
}
|
|
2883
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
2884
|
+
return message.tool_calls.some((toolCall) => {
|
|
2885
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
2886
|
+
return toolName === "summarize_context";
|
|
2887
|
+
});
|
|
2082
2888
|
}
|
|
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;
|
|
2889
|
+
return false;
|
|
2089
2890
|
}
|
|
2090
|
-
|
|
2091
|
-
// src/agent.ts
|
|
2092
2891
|
function createAgent(opts) {
|
|
2093
2892
|
const resolver = createResolverNode();
|
|
2094
2893
|
const agentCore = createAgentCoreNode(opts);
|
|
@@ -2131,6 +2930,7 @@ function createAgent(opts) {
|
|
|
2131
2930
|
tools: toolsBase,
|
|
2132
2931
|
guardrails: opts.guardrails,
|
|
2133
2932
|
systemPrompt: void 0,
|
|
2933
|
+
todoListPrompt: opts.todoListPrompt,
|
|
2134
2934
|
limits: opts.limits,
|
|
2135
2935
|
useTodoList: void 0,
|
|
2136
2936
|
outputSchema: opts.outputSchema,
|
|
@@ -2206,6 +3006,18 @@ function createAgent(opts) {
|
|
|
2206
3006
|
};
|
|
2207
3007
|
while (iterations < iterationLimit) {
|
|
2208
3008
|
iterations++;
|
|
3009
|
+
const traceSession = state.ctx?.__traceSession;
|
|
3010
|
+
if (traceSession) {
|
|
3011
|
+
const iterEvent = recordTraceEvent(traceSession, {
|
|
3012
|
+
type: "agent_iteration",
|
|
3013
|
+
label: `Iteration ${iterations}`,
|
|
3014
|
+
actor: { scope: "agent", name: opts.name || "agent", role: "orchestrator" },
|
|
3015
|
+
parentSpanId: traceSession.rootSpanId
|
|
3016
|
+
});
|
|
3017
|
+
if (iterEvent) {
|
|
3018
|
+
traceSession.currentIterationSpanId = iterEvent.spanId;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
2209
3021
|
if (cancelIfRequested("loop")) break;
|
|
2210
3022
|
const skippingAgent = resumeStage === "tools";
|
|
2211
3023
|
if (!skippingAgent) {
|
|
@@ -2256,11 +3068,21 @@ function createAgent(opts) {
|
|
|
2256
3068
|
maxTok = 5e4;
|
|
2257
3069
|
}
|
|
2258
3070
|
if (maxTok) {
|
|
2259
|
-
const
|
|
3071
|
+
const tokCountMessages = (state.messages || []).filter((message) => {
|
|
3072
|
+
if (isSyntheticSummaryMessage(message)) return false;
|
|
3073
|
+
if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
|
|
3074
|
+
return true;
|
|
3075
|
+
});
|
|
3076
|
+
const tokenCount = countMessagesTokens(tokCountMessages);
|
|
2260
3077
|
if (tokenCount > maxTok) {
|
|
2261
|
-
const
|
|
2262
|
-
|
|
2263
|
-
|
|
3078
|
+
const hasFreshSummary = (state.messages || []).some(
|
|
3079
|
+
(m) => m.role === "tool" && m.content === "SUMMARIZED"
|
|
3080
|
+
);
|
|
3081
|
+
if (hasFreshSummary && tokenCount <= maxTok * 1.15) ; else {
|
|
3082
|
+
const ctx = { ...state.ctx || {}, __needsSummarization: true };
|
|
3083
|
+
state = { ...state, ctx };
|
|
3084
|
+
break;
|
|
3085
|
+
}
|
|
2264
3086
|
}
|
|
2265
3087
|
}
|
|
2266
3088
|
onProgress?.({ stage: "agent", message: "Invoking model" });
|
|
@@ -2498,7 +3320,11 @@ function createAgent(opts) {
|
|
|
2498
3320
|
schema,
|
|
2499
3321
|
func: async ({ input }) => {
|
|
2500
3322
|
const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
|
|
2501
|
-
return {
|
|
3323
|
+
return {
|
|
3324
|
+
content: res.content,
|
|
3325
|
+
output: res.output,
|
|
3326
|
+
summary: res.state?.summaries?.[res.state.summaries.length - 1]
|
|
3327
|
+
};
|
|
2502
3328
|
}
|
|
2503
3329
|
});
|
|
2504
3330
|
},
|
|
@@ -2539,6 +3365,7 @@ function createAgentNode(opts) {
|
|
|
2539
3365
|
model: opts.model,
|
|
2540
3366
|
tools: opts.tools || [],
|
|
2541
3367
|
systemPrompt: opts.systemPrompt,
|
|
3368
|
+
todoListPrompt: opts.todoListPrompt,
|
|
2542
3369
|
limits: opts.limits,
|
|
2543
3370
|
useTodoList: opts.useTodoList,
|
|
2544
3371
|
outputSchema: opts.outputSchema
|
|
@@ -2556,7 +3383,8 @@ function createAgentNode(opts) {
|
|
|
2556
3383
|
content: buildSystemPrompt(
|
|
2557
3384
|
[runtime.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
|
|
2558
3385
|
runtime.useTodoList === true,
|
|
2559
|
-
runtime.name || "Agent"
|
|
3386
|
+
runtime.name || "Agent",
|
|
3387
|
+
runtime.todoListPrompt
|
|
2560
3388
|
)
|
|
2561
3389
|
};
|
|
2562
3390
|
const messages = [systemMsg, ...state.messages];
|
|
@@ -2638,9 +3466,119 @@ function createAgentNode(opts) {
|
|
|
2638
3466
|
};
|
|
2639
3467
|
}
|
|
2640
3468
|
|
|
3469
|
+
// src/smart/contextPolicy.ts
|
|
3470
|
+
function messageText(message) {
|
|
3471
|
+
if (typeof message.content === "string") return message.content;
|
|
3472
|
+
if (Array.isArray(message.content)) {
|
|
3473
|
+
return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? JSON.stringify(part)).join(" ");
|
|
3474
|
+
}
|
|
3475
|
+
return "";
|
|
3476
|
+
}
|
|
3477
|
+
function collectRecentTurns(messages, lastTurnsToKeep) {
|
|
3478
|
+
if (lastTurnsToKeep <= 0) return [];
|
|
3479
|
+
const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
|
|
3480
|
+
const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
|
|
3481
|
+
let userTurnCount = 0;
|
|
3482
|
+
const collected = [];
|
|
3483
|
+
for (let index = body.length - 1; index >= 0; index -= 1) {
|
|
3484
|
+
const message = body[index];
|
|
3485
|
+
collected.unshift(message);
|
|
3486
|
+
if (message.role === "user") {
|
|
3487
|
+
userTurnCount += 1;
|
|
3488
|
+
if (userTurnCount >= lastTurnsToKeep) break;
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
return [...systemPrefix, ...collected];
|
|
3492
|
+
}
|
|
3493
|
+
function renderStructuredSummary(summary) {
|
|
3494
|
+
if (!summary) return "";
|
|
3495
|
+
const lines = ["Context summary:"];
|
|
3496
|
+
if (summary.stable_facts.length > 0) {
|
|
3497
|
+
lines.push("Stable facts:");
|
|
3498
|
+
lines.push(...summary.stable_facts.map((fact) => `- ${fact.key}: ${fact.value}`));
|
|
3499
|
+
}
|
|
3500
|
+
if (summary.active_goals.length > 0) {
|
|
3501
|
+
lines.push("Active goals:");
|
|
3502
|
+
lines.push(...summary.active_goals.map((goal) => `- ${goal}`));
|
|
3503
|
+
}
|
|
3504
|
+
if (summary.open_questions.length > 0) {
|
|
3505
|
+
lines.push("Open questions:");
|
|
3506
|
+
lines.push(...summary.open_questions.map((question) => `- ${question}`));
|
|
3507
|
+
}
|
|
3508
|
+
if (summary.discarded_obsolete.length > 0) {
|
|
3509
|
+
lines.push("Discarded obsolete:");
|
|
3510
|
+
lines.push(...summary.discarded_obsolete.map((item) => `- ${item}`));
|
|
3511
|
+
}
|
|
3512
|
+
return lines.join("\n");
|
|
3513
|
+
}
|
|
3514
|
+
function renderMemoryBlock(facts) {
|
|
3515
|
+
if (!facts || facts.length === 0) return "";
|
|
3516
|
+
return [
|
|
3517
|
+
"Retrieved memory:",
|
|
3518
|
+
...facts.map((fact) => `- ${fact.key}: ${fact.value}`)
|
|
3519
|
+
].join("\n");
|
|
3520
|
+
}
|
|
3521
|
+
function clampToBudget(messages, maxContextTokens) {
|
|
3522
|
+
let working = [...messages];
|
|
3523
|
+
while (working.length > 2) {
|
|
3524
|
+
const tokenCount = countApproxTokens(working.map(messageText).join("\n"));
|
|
3525
|
+
if (tokenCount <= maxContextTokens) return working;
|
|
3526
|
+
const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
|
|
3527
|
+
if (firstNonSystem < 0) break;
|
|
3528
|
+
working.splice(firstNonSystem, 1);
|
|
3529
|
+
}
|
|
3530
|
+
return working;
|
|
3531
|
+
}
|
|
3532
|
+
function buildModelMessages(state, config) {
|
|
3533
|
+
const rawMessages = state.messages || [];
|
|
3534
|
+
if (config.context.policy === "raw") {
|
|
3535
|
+
return clampToBudget(rawMessages, config.limits.maxContextTokens);
|
|
3536
|
+
}
|
|
3537
|
+
const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
|
|
3538
|
+
const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
|
|
3539
|
+
const summaryText = renderStructuredSummary(latestSummary);
|
|
3540
|
+
const memoryText = renderMemoryBlock(state.memoryFacts);
|
|
3541
|
+
const systemMessage2 = recentMessages[0]?.role === "system" ? recentMessages[0] : void 0;
|
|
3542
|
+
const body = systemMessage2 ? recentMessages.slice(1) : recentMessages;
|
|
3543
|
+
const syntheticContextMessages = [];
|
|
3544
|
+
if (summaryText) {
|
|
3545
|
+
syntheticContextMessages.push({ role: "system", name: "context_summary", content: summaryText });
|
|
3546
|
+
}
|
|
3547
|
+
if (memoryText) {
|
|
3548
|
+
syntheticContextMessages.push({ role: "system", name: "memory_context", content: memoryText });
|
|
3549
|
+
}
|
|
3550
|
+
const assembled = [
|
|
3551
|
+
...systemMessage2 ? [systemMessage2] : [],
|
|
3552
|
+
...syntheticContextMessages,
|
|
3553
|
+
...config.context.policy === "summary_only" ? body.filter((message) => message.role === "user" || message.role === "assistant").slice(-2) : body
|
|
3554
|
+
];
|
|
3555
|
+
return clampToBudget(assembled, config.limits.maxContextTokens);
|
|
3556
|
+
}
|
|
3557
|
+
function estimateContextRotScore(state) {
|
|
3558
|
+
const summaryCount = state.summaryRecords?.length || 0;
|
|
3559
|
+
const archivedCount = state.toolHistoryArchived?.length || 0;
|
|
3560
|
+
const messageCount = state.messages?.length || 0;
|
|
3561
|
+
const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && message.content === "SUMMARIZED").length || 0;
|
|
3562
|
+
const numerator = summaryCount * 2 + archivedCount + summarizedMessages;
|
|
3563
|
+
const denominator = Math.max(messageCount, 1);
|
|
3564
|
+
return Number((numerator / denominator).toFixed(3));
|
|
3565
|
+
}
|
|
3566
|
+
|
|
2641
3567
|
// src/nodes/contextSummarize.ts
|
|
2642
3568
|
var systemMessage = (content) => ({ role: "system", content });
|
|
2643
3569
|
var humanMessage = (content) => ({ role: "user", content });
|
|
3570
|
+
function isSyntheticSummaryToolMessage(message) {
|
|
3571
|
+
return message.role === "tool" && message.name === "summarize_context";
|
|
3572
|
+
}
|
|
3573
|
+
function isSyntheticSummaryAssistantMessage(message) {
|
|
3574
|
+
if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) {
|
|
3575
|
+
return false;
|
|
3576
|
+
}
|
|
3577
|
+
return message.tool_calls.some((toolCall) => {
|
|
3578
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
3579
|
+
return toolName === "summarize_context";
|
|
3580
|
+
});
|
|
3581
|
+
}
|
|
2644
3582
|
function validateMessageSequence(messages) {
|
|
2645
3583
|
const validated = [];
|
|
2646
3584
|
const pendingToolCallIds = /* @__PURE__ */ new Set();
|
|
@@ -2666,33 +3604,137 @@ function validateMessageSequence(messages) {
|
|
|
2666
3604
|
}
|
|
2667
3605
|
return validated;
|
|
2668
3606
|
}
|
|
2669
|
-
function
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
3607
|
+
function extractJsonObject(text) {
|
|
3608
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
3609
|
+
if (fenced?.[1]) return fenced[1].trim();
|
|
3610
|
+
const start = text.indexOf("{");
|
|
3611
|
+
const end = text.lastIndexOf("}");
|
|
3612
|
+
if (start >= 0 && end > start) {
|
|
3613
|
+
return text.slice(start, end + 1);
|
|
3614
|
+
}
|
|
3615
|
+
return null;
|
|
3616
|
+
}
|
|
3617
|
+
function normalizeStructuredSummary(input, fallbackText) {
|
|
3618
|
+
const summary = {
|
|
3619
|
+
stable_facts: Array.isArray(input?.stable_facts) ? input.stable_facts.filter((fact) => fact && typeof fact === "object").map((fact) => ({
|
|
3620
|
+
key: String(fact.key || "unknown"),
|
|
3621
|
+
value: String(fact.value || ""),
|
|
3622
|
+
confidence: typeof fact.confidence === "number" ? fact.confidence : 0.7,
|
|
3623
|
+
source: fact.source ? String(fact.source) : void 0
|
|
3624
|
+
})) : [],
|
|
3625
|
+
active_goals: Array.isArray(input?.active_goals) ? input.active_goals.map((goal) => String(goal)) : [],
|
|
3626
|
+
open_questions: Array.isArray(input?.open_questions) ? input.open_questions.map((question) => String(question)) : [],
|
|
3627
|
+
discarded_obsolete: Array.isArray(input?.discarded_obsolete || input?.obsolete_discarded) ? (input.discarded_obsolete || input.obsolete_discarded).map((item) => String(item)) : [],
|
|
3628
|
+
rawSummary: typeof input?.rawSummary === "string" ? input.rawSummary : fallbackText
|
|
3629
|
+
};
|
|
3630
|
+
if (summary.stable_facts.length === 0 && fallbackText) {
|
|
3631
|
+
const importantLines = fallbackText.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 8);
|
|
3632
|
+
summary.stable_facts = importantLines.map((line, index) => ({
|
|
3633
|
+
key: `fact_${index + 1}`,
|
|
3634
|
+
value: line,
|
|
3635
|
+
confidence: 0.5
|
|
3636
|
+
}));
|
|
3637
|
+
}
|
|
3638
|
+
return summary;
|
|
3639
|
+
}
|
|
3640
|
+
function normalizeFactKeySegment(value) {
|
|
3641
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "item";
|
|
3642
|
+
}
|
|
3643
|
+
function extractCanonicalFacts(messages) {
|
|
3644
|
+
const facts = /* @__PURE__ */ new Map();
|
|
3645
|
+
for (const message of messages) {
|
|
3646
|
+
const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.map((part) => part?.text ?? part?.content ?? String(part ?? "")).join("\n") : "";
|
|
3647
|
+
if (!content) continue;
|
|
3648
|
+
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3649
|
+
for (const line of lines) {
|
|
3650
|
+
if (!/^[A-Z0-9_]+\|/.test(line) || !line.includes("=")) continue;
|
|
3651
|
+
const segments = line.split("|").map((segment) => segment.trim()).filter(Boolean);
|
|
3652
|
+
const label = segments[0];
|
|
3653
|
+
const entries = segments.slice(1).map((segment) => {
|
|
3654
|
+
const eqIndex = segment.indexOf("=");
|
|
3655
|
+
if (eqIndex <= 0) return null;
|
|
3656
|
+
return {
|
|
3657
|
+
key: segment.slice(0, eqIndex).trim(),
|
|
3658
|
+
value: segment.slice(eqIndex + 1).trim()
|
|
3659
|
+
};
|
|
3660
|
+
}).filter((entry) => Boolean(entry?.key));
|
|
3661
|
+
if (entries.length === 0) continue;
|
|
3662
|
+
const identifier = entries.find((entry) => ["code", "id", "project", "name", "key"].includes(entry.key.toLowerCase()))?.value;
|
|
3663
|
+
const keyPrefix = [normalizeFactKeySegment(label), identifier ? normalizeFactKeySegment(identifier) : void 0].filter(Boolean).join(".");
|
|
3664
|
+
for (const entry of entries) {
|
|
3665
|
+
const normalizedEntryKey = normalizeFactKeySegment(entry.key);
|
|
3666
|
+
const factKey = keyPrefix ? `${keyPrefix}.${normalizedEntryKey}` : `${normalizeFactKeySegment(label)}.${normalizedEntryKey}`;
|
|
3667
|
+
facts.set(factKey, {
|
|
3668
|
+
key: factKey,
|
|
3669
|
+
value: entry.value,
|
|
3670
|
+
confidence: 0.98,
|
|
3671
|
+
source: "canonical_tool_output"
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
return [...facts.values()];
|
|
3677
|
+
}
|
|
3678
|
+
function mergeStableFacts(summary, additionalFacts) {
|
|
3679
|
+
if (additionalFacts.length === 0) return summary;
|
|
3680
|
+
const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
|
|
3681
|
+
for (const fact of additionalFacts) {
|
|
3682
|
+
const existing = merged.get(fact.key);
|
|
3683
|
+
if (!existing || (fact.confidence || 0) >= (existing.confidence || 0)) {
|
|
3684
|
+
merged.set(fact.key, fact);
|
|
3685
|
+
}
|
|
2679
3686
|
}
|
|
2680
3687
|
return {
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
summaryPromptMaxTokens: 8e3,
|
|
2684
|
-
promptTemplate: void 0
|
|
3688
|
+
...summary,
|
|
3689
|
+
stable_facts: [...merged.values()]
|
|
2685
3690
|
};
|
|
2686
3691
|
}
|
|
3692
|
+
function runIntegrityCheck(previous, current) {
|
|
3693
|
+
const notes = [];
|
|
3694
|
+
const previousKeys = new Set((previous?.stable_facts || []).map((fact) => fact.key));
|
|
3695
|
+
const currentKeys = new Set(current.stable_facts.map((fact) => fact.key));
|
|
3696
|
+
const obsoleteKeys = new Set(current.discarded_obsolete);
|
|
3697
|
+
const criticalFactLoss = [...previousKeys].some((key) => !obsoleteKeys.has(key) && !currentKeys.has(key));
|
|
3698
|
+
const obsoleteFactRevived = current.stable_facts.some((fact) => obsoleteKeys.has(fact.key));
|
|
3699
|
+
if (criticalFactLoss) {
|
|
3700
|
+
notes.push("Missing previously retained facts; merged forward during integrity repair.");
|
|
3701
|
+
}
|
|
3702
|
+
if (obsoleteFactRevived) {
|
|
3703
|
+
notes.push("Obsolete facts reappeared; removed during integrity repair.");
|
|
3704
|
+
}
|
|
3705
|
+
return {
|
|
3706
|
+
passed: !criticalFactLoss && !obsoleteFactRevived,
|
|
3707
|
+
criticalFactLoss,
|
|
3708
|
+
obsoleteFactRevived,
|
|
3709
|
+
notes
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
function repairStructuredSummary(previous, current, integrity) {
|
|
3713
|
+
if (!previous) return current;
|
|
3714
|
+
const nextFacts = [...current.stable_facts];
|
|
3715
|
+
if (integrity.criticalFactLoss) {
|
|
3716
|
+
const existingKeys = new Set(nextFacts.map((fact) => fact.key));
|
|
3717
|
+
for (const fact of previous.stable_facts) {
|
|
3718
|
+
if (!existingKeys.has(fact.key) && !current.discarded_obsolete.includes(fact.key)) {
|
|
3719
|
+
nextFacts.push(fact);
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
const filteredFacts = integrity.obsoleteFactRevived ? nextFacts.filter((fact) => !current.discarded_obsolete.includes(fact.key)) : nextFacts;
|
|
3724
|
+
return { ...current, stable_facts: filteredFacts };
|
|
3725
|
+
}
|
|
2687
3726
|
function createContextSummarizeNode(opts) {
|
|
2688
3727
|
return async (state) => {
|
|
2689
|
-
const
|
|
2690
|
-
|
|
3728
|
+
const resolved = getResolvedSmartConfig(opts, state.agent);
|
|
3729
|
+
const config = resolved.summarization;
|
|
3730
|
+
if (!config.enable) return {};
|
|
2691
3731
|
const model = opts.model;
|
|
2692
3732
|
const messages = state.messages || [];
|
|
2693
3733
|
const onEvent = state.ctx?.__onEvent;
|
|
2694
3734
|
const traceSession = state.ctx?.__traceSession;
|
|
2695
|
-
const compressableMessages = messages.filter(
|
|
3735
|
+
const compressableMessages = messages.filter(
|
|
3736
|
+
(m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
|
|
3737
|
+
);
|
|
2696
3738
|
if (compressableMessages.length === 0) {
|
|
2697
3739
|
return {};
|
|
2698
3740
|
}
|
|
@@ -2704,6 +3746,9 @@ function createContextSummarizeNode(opts) {
|
|
|
2704
3746
|
let accumulatedTokens = 0;
|
|
2705
3747
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2706
3748
|
const m = messages[i];
|
|
3749
|
+
if (isSyntheticSummaryToolMessage(m) || isSyntheticSummaryAssistantMessage(m)) {
|
|
3750
|
+
continue;
|
|
3751
|
+
}
|
|
2707
3752
|
let content = "";
|
|
2708
3753
|
if (typeof m.content === "string") content = m.content;
|
|
2709
3754
|
else if (Array.isArray(m.content)) {
|
|
@@ -2715,39 +3760,60 @@ function createContextSummarizeNode(opts) {
|
|
|
2715
3760
|
}
|
|
2716
3761
|
let prefix = `${m.role.toUpperCase()}`;
|
|
2717
3762
|
if (m.name) prefix += ` (${m.name})`;
|
|
2718
|
-
const
|
|
2719
|
-
const messageTokens = countApproxTokens(
|
|
3763
|
+
const messageText2 = `${prefix}: ${content}`;
|
|
3764
|
+
const messageTokens = countApproxTokens(messageText2);
|
|
2720
3765
|
if (accumulatedTokens + messageTokens > availableTokensForConversation) {
|
|
2721
3766
|
messageTexts.unshift("[Earlier conversation history omitted for brevity]");
|
|
2722
3767
|
break;
|
|
2723
3768
|
}
|
|
2724
|
-
messageTexts.unshift(
|
|
3769
|
+
messageTexts.unshift(messageText2);
|
|
2725
3770
|
accumulatedTokens += messageTokens;
|
|
2726
3771
|
}
|
|
2727
3772
|
const conversationText = messageTexts.join("\n\n");
|
|
3773
|
+
const canonicalFacts = extractCanonicalFacts(compressableMessages);
|
|
2728
3774
|
const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
|
|
2729
|
-
const
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
3775
|
+
const previousStructuredSummary = Array.isArray(state.summaryRecords) && state.summaryRecords.length > 0 ? state.summaryRecords[state.summaryRecords.length - 1] : void 0;
|
|
3776
|
+
const defaultPrompt = `Summarize the conversation into strict JSON.
|
|
3777
|
+
|
|
3778
|
+
Return exactly one JSON object with this schema:
|
|
3779
|
+
{
|
|
3780
|
+
"stable_facts": [{ "key": string, "value": string, "confidence": number }],
|
|
3781
|
+
"active_goals": [string],
|
|
3782
|
+
"open_questions": [string],
|
|
3783
|
+
"discarded_obsolete": [string],
|
|
3784
|
+
"rawSummary": string
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
Rules:
|
|
3788
|
+
- Keep stable_facts only for facts that future turns must remember.
|
|
3789
|
+
- Put invalidated or superseded fact keys in discarded_obsolete.
|
|
3790
|
+
- Preserve active_goals still relevant to the user.
|
|
3791
|
+
- Preserve unresolved questions.
|
|
3792
|
+
- Do not include prose outside JSON.
|
|
3793
|
+
- Target compression ratio: ${config.summaryCompressionRatioTarget}.
|
|
3794
|
+
- Summary mode: ${config.summaryMode}.
|
|
2735
3795
|
|
|
2736
|
-
Previous summary
|
|
3796
|
+
Previous summary text:
|
|
2737
3797
|
${previousSummary || "(none)"}
|
|
2738
3798
|
|
|
3799
|
+
Previous structured summary:
|
|
3800
|
+
${previousStructuredSummary ? JSON.stringify(previousStructuredSummary) : "(none)"}
|
|
3801
|
+
|
|
2739
3802
|
Conversation:
|
|
2740
3803
|
${conversationText}
|
|
2741
3804
|
|
|
2742
|
-
|
|
3805
|
+
Canonical facts extracted from tool outputs:
|
|
3806
|
+
${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fact.value}`).join("\n") : "(none)"}`;
|
|
2743
3807
|
const template = config.promptTemplate || defaultPrompt;
|
|
2744
3808
|
const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
|
|
2745
3809
|
const summaryPrompt = [
|
|
2746
|
-
systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
|
|
3810
|
+
systemMessage("You are a helpful assistant that summarizes conversation history efficiently. Return strict JSON only."),
|
|
2747
3811
|
humanMessage(promptBody)
|
|
2748
3812
|
];
|
|
2749
3813
|
const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
|
|
2750
3814
|
let summaryText = "Summary unavailable.";
|
|
3815
|
+
let structuredSummary;
|
|
3816
|
+
let integrity;
|
|
2751
3817
|
let outputTokensEstimate = 0;
|
|
2752
3818
|
let durationMs = 0;
|
|
2753
3819
|
let inputTokensActual;
|
|
@@ -2756,10 +3822,23 @@ Summary:`;
|
|
|
2756
3822
|
let totalTokensActual;
|
|
2757
3823
|
const startTime = Date.now();
|
|
2758
3824
|
try {
|
|
3825
|
+
if (!model || typeof model.invoke !== "function") {
|
|
3826
|
+
throw new Error("Summarization model is unavailable.");
|
|
3827
|
+
}
|
|
2759
3828
|
const response = await model.invoke(summaryPrompt);
|
|
2760
3829
|
durationMs = Date.now() - startTime;
|
|
2761
3830
|
summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
|
|
2762
3831
|
outputTokensEstimate = countApproxTokens(summaryText);
|
|
3832
|
+
const jsonText = extractJsonObject(summaryText);
|
|
3833
|
+
const parsed = jsonText ? JSON.parse(jsonText) : void 0;
|
|
3834
|
+
structuredSummary = normalizeStructuredSummary(parsed, summaryText);
|
|
3835
|
+
structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
|
|
3836
|
+
integrity = config.integrityCheck ? runIntegrityCheck(previousStructuredSummary, structuredSummary) : { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: [] };
|
|
3837
|
+
if (integrity && !integrity.passed) {
|
|
3838
|
+
structuredSummary = repairStructuredSummary(previousStructuredSummary, structuredSummary, integrity);
|
|
3839
|
+
integrity = { ...integrity, passed: true };
|
|
3840
|
+
}
|
|
3841
|
+
summaryText = renderStructuredSummary(structuredSummary);
|
|
2763
3842
|
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
|
|
2764
3843
|
const normalized = normalizeUsage(rawUsage);
|
|
2765
3844
|
if (normalized) {
|
|
@@ -2770,17 +3849,22 @@ Summary:`;
|
|
|
2770
3849
|
}
|
|
2771
3850
|
} catch (err) {
|
|
2772
3851
|
durationMs = Date.now() - startTime;
|
|
2773
|
-
|
|
3852
|
+
structuredSummary = normalizeStructuredSummary(void 0, previousSummary || conversationText.slice(0, 800));
|
|
3853
|
+
structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
|
|
3854
|
+
integrity = { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: ["Fallback summary generated locally."] };
|
|
3855
|
+
summaryText = renderStructuredSummary(structuredSummary);
|
|
2774
3856
|
const errorEvent = {
|
|
2775
3857
|
type: "summarization",
|
|
2776
|
-
summary:
|
|
2777
|
-
messagesCompressed:
|
|
3858
|
+
summary: summaryText,
|
|
3859
|
+
messagesCompressed: compressableMessages.length,
|
|
2778
3860
|
inputTokens: inputTokensEstimate,
|
|
2779
|
-
outputTokens:
|
|
3861
|
+
outputTokens: countApproxTokens(summaryText),
|
|
2780
3862
|
durationMs,
|
|
2781
3863
|
previousSummary: previousSummary || void 0,
|
|
2782
3864
|
tokenCountBefore,
|
|
2783
|
-
tokenCountAfter: tokenCountBefore
|
|
3865
|
+
tokenCountAfter: tokenCountBefore,
|
|
3866
|
+
structuredSummary,
|
|
3867
|
+
integrity
|
|
2784
3868
|
};
|
|
2785
3869
|
onEvent?.(errorEvent);
|
|
2786
3870
|
recordTraceEvent(traceSession, {
|
|
@@ -2790,11 +3874,10 @@ Summary:`;
|
|
|
2790
3874
|
status: "error",
|
|
2791
3875
|
durationMs,
|
|
2792
3876
|
inputTokens: inputTokensEstimate,
|
|
2793
|
-
outputTokens:
|
|
3877
|
+
outputTokens: countApproxTokens(summaryText),
|
|
2794
3878
|
error: { message: err?.message || String(err), stack: err?.stack },
|
|
2795
3879
|
messageList: sanitizeTracePayload(summaryPrompt)
|
|
2796
3880
|
});
|
|
2797
|
-
return {};
|
|
2798
3881
|
}
|
|
2799
3882
|
const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
|
|
2800
3883
|
messages.forEach((m, idx) => {
|
|
@@ -2808,6 +3891,9 @@ Summary:`;
|
|
|
2808
3891
|
});
|
|
2809
3892
|
const newMessages = messages.map((m) => {
|
|
2810
3893
|
if (m.role === "tool") {
|
|
3894
|
+
if (isSyntheticSummaryToolMessage(m)) {
|
|
3895
|
+
return m;
|
|
3896
|
+
}
|
|
2811
3897
|
const toolCallId = m.tool_call_id;
|
|
2812
3898
|
if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
|
|
2813
3899
|
return null;
|
|
@@ -2845,6 +3931,7 @@ Summary:`;
|
|
|
2845
3931
|
content: summaryText
|
|
2846
3932
|
};
|
|
2847
3933
|
const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
|
|
3934
|
+
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
3935
|
const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
|
|
2849
3936
|
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
3937
|
const tokenCountAfter = countApproxTokens(allTextAfter);
|
|
@@ -2860,8 +3947,10 @@ Summary:`;
|
|
|
2860
3947
|
previousSummary: previousSummary || void 0,
|
|
2861
3948
|
tokenCountBefore,
|
|
2862
3949
|
tokenCountAfter,
|
|
2863
|
-
archivedCount: compressableMessages.length
|
|
3950
|
+
archivedCount: compressableMessages.length,
|
|
2864
3951
|
// deprecated field for backward compatibility
|
|
3952
|
+
structuredSummary,
|
|
3953
|
+
integrity
|
|
2865
3954
|
};
|
|
2866
3955
|
onEvent?.(summarizationEvent);
|
|
2867
3956
|
recordTraceEvent(traceSession, {
|
|
@@ -2885,12 +3974,19 @@ Summary:`;
|
|
|
2885
3974
|
tokensSaved: tokenCountBefore - tokenCountAfter,
|
|
2886
3975
|
previousSummaryLength: previousSummary?.length || 0,
|
|
2887
3976
|
usageFromModel: inputTokensActual !== void 0,
|
|
2888
|
-
cachedInputTokens
|
|
3977
|
+
cachedInputTokens,
|
|
3978
|
+
integrity
|
|
2889
3979
|
}
|
|
2890
3980
|
});
|
|
2891
3981
|
return {
|
|
2892
3982
|
messages: finalMessages,
|
|
2893
|
-
summaries
|
|
3983
|
+
summaries,
|
|
3984
|
+
summaryRecords,
|
|
3985
|
+
watchdog: {
|
|
3986
|
+
...state.watchdog || {},
|
|
3987
|
+
compactions: (state.watchdog?.compactions || 0) + 1,
|
|
3988
|
+
lastAction: "summarized"
|
|
3989
|
+
}
|
|
2894
3990
|
};
|
|
2895
3991
|
};
|
|
2896
3992
|
}
|
|
@@ -2913,38 +4009,200 @@ async function applyTokenLimits({
|
|
|
2913
4009
|
}
|
|
2914
4010
|
return { ...state, messages: newMessages };
|
|
2915
4011
|
}
|
|
4012
|
+
var todoStatusSchema = zod.z.enum(["not-started", "in-progress", "completed", "blocked"]);
|
|
4013
|
+
var todoWriteItemSchema = zod.z.object({
|
|
4014
|
+
id: zod.z.number().int().min(1).describe("Sequential id starting from 1"),
|
|
4015
|
+
step: zod.z.string().min(1).optional(),
|
|
4016
|
+
title: zod.z.string().min(1).optional(),
|
|
4017
|
+
description: zod.z.string().min(1).optional(),
|
|
4018
|
+
owner: zod.z.string().min(1).optional(),
|
|
4019
|
+
exitCriteria: zod.z.string().min(1).optional(),
|
|
4020
|
+
status: todoStatusSchema,
|
|
4021
|
+
evidence: zod.z.string().max(300).optional()
|
|
4022
|
+
});
|
|
4023
|
+
var todoUpdateItemSchema = zod.z.object({
|
|
4024
|
+
id: zod.z.number().int().min(1).describe("Existing todo id to update"),
|
|
4025
|
+
step: zod.z.string().min(1).optional(),
|
|
4026
|
+
title: zod.z.string().min(1).optional(),
|
|
4027
|
+
description: zod.z.string().min(1).optional(),
|
|
4028
|
+
owner: zod.z.string().min(1).optional(),
|
|
4029
|
+
exitCriteria: zod.z.string().min(1).optional(),
|
|
4030
|
+
status: todoStatusSchema.optional(),
|
|
4031
|
+
evidence: zod.z.string().max(300).optional()
|
|
4032
|
+
});
|
|
4033
|
+
function normalizeTodoItem(item, existing) {
|
|
4034
|
+
const id = item.id ?? existing?.id;
|
|
4035
|
+
const step = item.step || item.title || item.description || existing?.step || existing?.title || existing?.description || `Step ${id}`;
|
|
4036
|
+
const title = item.title || item.step || existing?.title || existing?.step || `Step ${id}`;
|
|
4037
|
+
const description = item.description || item.step || item.title || existing?.description || existing?.step || existing?.title || `Step ${id}`;
|
|
4038
|
+
return {
|
|
4039
|
+
...existing,
|
|
4040
|
+
...item,
|
|
4041
|
+
id,
|
|
4042
|
+
step,
|
|
4043
|
+
title,
|
|
4044
|
+
description,
|
|
4045
|
+
owner: item.owner || existing?.owner || "agent",
|
|
4046
|
+
exitCriteria: item.exitCriteria || existing?.exitCriteria || description || `Complete step ${id}`,
|
|
4047
|
+
status: item.status || existing?.status,
|
|
4048
|
+
evidence: item.evidence ?? existing?.evidence
|
|
4049
|
+
};
|
|
4050
|
+
}
|
|
4051
|
+
function validatePlanInvariants(todoList, requireSequentialIds) {
|
|
4052
|
+
const ids = todoList.map((item) => item.id);
|
|
4053
|
+
const uniqueIds = new Set(ids);
|
|
4054
|
+
if (uniqueIds.size !== ids.length) {
|
|
4055
|
+
return { ok: false, error: "Todo ids must be unique." };
|
|
4056
|
+
}
|
|
4057
|
+
if (requireSequentialIds) {
|
|
4058
|
+
const expectedIds = Array.from({ length: todoList.length }, (_, index) => index + 1);
|
|
4059
|
+
const isSequential = ids.every((id, index) => id === expectedIds[index]);
|
|
4060
|
+
if (!isSequential) {
|
|
4061
|
+
return { ok: false, error: "Todo ids must be sequential starting from 1 for write operations." };
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
const inProgressCount = todoList.filter((item) => item.status === "in-progress").length;
|
|
4065
|
+
if (inProgressCount > 1) {
|
|
4066
|
+
return { ok: false, error: "Only one todo item may be in-progress at a time." };
|
|
4067
|
+
}
|
|
4068
|
+
return { ok: true };
|
|
4069
|
+
}
|
|
4070
|
+
function calculateAdherenceScore(todoList) {
|
|
4071
|
+
const completedCount = todoList.filter((item) => item.status === "completed").length;
|
|
4072
|
+
return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
|
|
4073
|
+
}
|
|
2916
4074
|
function createContextTools(stateRef, opts) {
|
|
2917
4075
|
const tools = [];
|
|
2918
4076
|
if (opts?.planningEnabled) {
|
|
2919
4077
|
const manageTodo = createTool({
|
|
2920
4078
|
name: "manage_todo_list",
|
|
2921
|
-
description: "Manage a structured todo list to track progress and plan tasks throughout your coding session
|
|
4079
|
+
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
4080
|
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()
|
|
4081
|
+
operation: zod.z.enum(["write", "read", "update"]),
|
|
4082
|
+
expectedVersion: zod.z.number().int().min(0).optional(),
|
|
4083
|
+
todoList: zod.z.array(zod.z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
|
|
2931
4084
|
}),
|
|
2932
|
-
func: async ({ operation, todoList }) => {
|
|
4085
|
+
func: async ({ operation, expectedVersion, todoList }) => {
|
|
2933
4086
|
const onEvent = manageTodo._stateRef?.__onEvent;
|
|
4087
|
+
const currentVersion = stateRef.planVersion || 0;
|
|
2934
4088
|
if (operation === "read") {
|
|
2935
4089
|
const list = stateRef.todoList || [];
|
|
2936
|
-
onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list });
|
|
4090
|
+
onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
|
|
2937
4091
|
return list;
|
|
2938
4092
|
}
|
|
2939
|
-
if (
|
|
2940
|
-
|
|
4093
|
+
if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
|
|
4094
|
+
return {
|
|
4095
|
+
status: "error",
|
|
4096
|
+
operation,
|
|
4097
|
+
error: "Plan version mismatch. Read the latest plan and retry.",
|
|
4098
|
+
version: currentVersion,
|
|
4099
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4100
|
+
};
|
|
4101
|
+
}
|
|
4102
|
+
if (!Array.isArray(todoList)) {
|
|
4103
|
+
return {
|
|
4104
|
+
status: "error",
|
|
4105
|
+
operation,
|
|
4106
|
+
error: "todoList is required for write and update operations.",
|
|
4107
|
+
version: currentVersion,
|
|
4108
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4109
|
+
};
|
|
2941
4110
|
}
|
|
4111
|
+
if (operation === "write") {
|
|
4112
|
+
const parsed = zod.z.array(todoWriteItemSchema).safeParse(todoList);
|
|
4113
|
+
if (!parsed.success) {
|
|
4114
|
+
return {
|
|
4115
|
+
status: "error",
|
|
4116
|
+
operation,
|
|
4117
|
+
error: parsed.error.issues[0]?.message || "Invalid todoList for write.",
|
|
4118
|
+
version: currentVersion,
|
|
4119
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4120
|
+
};
|
|
4121
|
+
}
|
|
4122
|
+
const normalizedList = parsed.data.map((item) => normalizeTodoItem(item));
|
|
4123
|
+
const validation = validatePlanInvariants(normalizedList, true);
|
|
4124
|
+
if (!validation.ok) {
|
|
4125
|
+
return {
|
|
4126
|
+
status: "error",
|
|
4127
|
+
operation,
|
|
4128
|
+
error: validation.error,
|
|
4129
|
+
version: currentVersion,
|
|
4130
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4131
|
+
};
|
|
4132
|
+
}
|
|
4133
|
+
stateRef.todoList = normalizedList;
|
|
4134
|
+
} else if (operation === "update") {
|
|
4135
|
+
const parsed = zod.z.array(todoUpdateItemSchema).safeParse(todoList);
|
|
4136
|
+
if (!parsed.success) {
|
|
4137
|
+
return {
|
|
4138
|
+
status: "error",
|
|
4139
|
+
operation,
|
|
4140
|
+
error: parsed.error.issues[0]?.message || "Invalid todoList for update.",
|
|
4141
|
+
version: currentVersion,
|
|
4142
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4143
|
+
};
|
|
4144
|
+
}
|
|
4145
|
+
const currentList = Array.isArray(stateRef.todoList) ? stateRef.todoList : [];
|
|
4146
|
+
if (currentList.length === 0) {
|
|
4147
|
+
return {
|
|
4148
|
+
status: "error",
|
|
4149
|
+
operation,
|
|
4150
|
+
error: "No existing plan to update. Use write first.",
|
|
4151
|
+
version: currentVersion,
|
|
4152
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
4155
|
+
const patchIds = /* @__PURE__ */ new Set();
|
|
4156
|
+
for (const item of parsed.data) {
|
|
4157
|
+
if (patchIds.has(item.id)) {
|
|
4158
|
+
return {
|
|
4159
|
+
status: "error",
|
|
4160
|
+
operation,
|
|
4161
|
+
error: "Update payload contains duplicate todo ids.",
|
|
4162
|
+
version: currentVersion,
|
|
4163
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
patchIds.add(item.id);
|
|
4167
|
+
}
|
|
4168
|
+
const currentMap = new Map(currentList.map((item) => [item.id, item]));
|
|
4169
|
+
for (const item of parsed.data) {
|
|
4170
|
+
if (!currentMap.has(item.id)) {
|
|
4171
|
+
return {
|
|
4172
|
+
status: "error",
|
|
4173
|
+
operation,
|
|
4174
|
+
error: `Cannot update missing todo id ${item.id}. Use write to replace the plan.`,
|
|
4175
|
+
version: currentVersion,
|
|
4176
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4177
|
+
};
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
const mergedList = currentList.map((item) => {
|
|
4181
|
+
const patch = parsed.data.find((candidate) => candidate.id === item.id);
|
|
4182
|
+
return patch ? normalizeTodoItem(patch, item) : item;
|
|
4183
|
+
});
|
|
4184
|
+
const validation = validatePlanInvariants(mergedList, false);
|
|
4185
|
+
if (!validation.ok) {
|
|
4186
|
+
return {
|
|
4187
|
+
status: "error",
|
|
4188
|
+
operation,
|
|
4189
|
+
error: validation.error,
|
|
4190
|
+
version: currentVersion,
|
|
4191
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4192
|
+
};
|
|
4193
|
+
}
|
|
4194
|
+
stateRef.todoList = mergedList;
|
|
4195
|
+
}
|
|
4196
|
+
stateRef.planVersion = currentVersion + 1;
|
|
4197
|
+
stateRef.adherenceScore = calculateAdherenceScore(stateRef.todoList || []);
|
|
2942
4198
|
const payload = {
|
|
2943
4199
|
status: "ok",
|
|
2944
4200
|
operation,
|
|
2945
|
-
count: Array.isArray(todoList) ? todoList.length : void 0
|
|
4201
|
+
count: Array.isArray(todoList) ? todoList.length : void 0,
|
|
4202
|
+
version: stateRef.planVersion || 1,
|
|
4203
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
2946
4204
|
};
|
|
2947
|
-
onEvent?.({ type: "plan", source: "manage_todo_list", operation:
|
|
4205
|
+
onEvent?.({ type: "plan", source: "manage_todo_list", operation, todoList: stateRef.todoList, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
|
|
2948
4206
|
return payload;
|
|
2949
4207
|
}
|
|
2950
4208
|
});
|
|
@@ -2953,12 +4211,13 @@ function createContextTools(stateRef, opts) {
|
|
|
2953
4211
|
}
|
|
2954
4212
|
const getTool = createTool({
|
|
2955
4213
|
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.",
|
|
4214
|
+
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
4215
|
schema: zod.z.object({ executionId: zod.z.string().describe("Tool execution id") }),
|
|
2958
4216
|
func: async ({ executionId }) => {
|
|
2959
|
-
|
|
4217
|
+
const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
|
|
4218
|
+
let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));
|
|
2960
4219
|
if (!execution) {
|
|
2961
|
-
execution = stateRef.toolHistoryArchived?.find((t) => t
|
|
4220
|
+
execution = stateRef.toolHistoryArchived?.find((t) => matchesExecution(t));
|
|
2962
4221
|
}
|
|
2963
4222
|
if (execution) {
|
|
2964
4223
|
return execution.rawOutput || execution.output;
|
|
@@ -2989,19 +4248,33 @@ function createContextTools(stateRef, opts) {
|
|
|
2989
4248
|
}
|
|
2990
4249
|
|
|
2991
4250
|
// src/graph/decisions.ts
|
|
4251
|
+
function isSyntheticSummaryMessage2(message) {
|
|
4252
|
+
if (!message) return false;
|
|
4253
|
+
if (message.role === "tool" && message.name === "summarize_context") {
|
|
4254
|
+
return true;
|
|
4255
|
+
}
|
|
4256
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
4257
|
+
return message.tool_calls.some((toolCall) => {
|
|
4258
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
4259
|
+
return toolName === "summarize_context";
|
|
4260
|
+
});
|
|
4261
|
+
}
|
|
4262
|
+
return false;
|
|
4263
|
+
}
|
|
2992
4264
|
function needsSummarization(state, opts, summarizationEnabled) {
|
|
2993
4265
|
if (!summarizationEnabled) {
|
|
2994
4266
|
return false;
|
|
2995
4267
|
}
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
maxTok = opts.summarization.maxTokens;
|
|
4268
|
+
if (state.ctx?.__needsSummarization) {
|
|
4269
|
+
return true;
|
|
2999
4270
|
}
|
|
3000
|
-
|
|
3001
|
-
|
|
4271
|
+
const resolved = getResolvedSmartConfig(opts, state.agent);
|
|
4272
|
+
if (!resolved.summarization.enable) {
|
|
4273
|
+
return false;
|
|
3002
4274
|
}
|
|
4275
|
+
const maxTok = resolved.summarization.summaryTriggerTokens;
|
|
3003
4276
|
try {
|
|
3004
|
-
const allText = (state.messages || []).map((m) => {
|
|
4277
|
+
const allText = (state.messages || []).filter((message) => !isSyntheticSummaryMessage2(message)).map((m) => {
|
|
3005
4278
|
if (typeof m.content === "string") return m.content;
|
|
3006
4279
|
if (Array.isArray(m.content)) {
|
|
3007
4280
|
return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
|
|
@@ -3012,7 +4285,8 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
3012
4285
|
return "";
|
|
3013
4286
|
}).join("\n");
|
|
3014
4287
|
const tokenCount = countApproxTokens(allText);
|
|
3015
|
-
const
|
|
4288
|
+
const contextRotScore = state.watchdog?.contextRotScore || 0;
|
|
4289
|
+
const needsSum = tokenCount > maxTok || resolved.watchdog.enabled && resolved.watchdog.autoCompaction && contextRotScore >= resolved.watchdog.contextRotThreshold;
|
|
3016
4290
|
if (process.env.DEBUG_SUMMARIZATION) {
|
|
3017
4291
|
const msgCount = (state.messages || []).length;
|
|
3018
4292
|
const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
|
|
@@ -3022,10 +4296,10 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
3022
4296
|
});
|
|
3023
4297
|
console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
|
|
3024
4298
|
toolDetails.forEach((d) => console.log(` ${d}`));
|
|
3025
|
-
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
|
|
4299
|
+
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Context rot: ${contextRotScore}, Needs summarization: ${needsSum}`);
|
|
3026
4300
|
}
|
|
3027
4301
|
return needsSum;
|
|
3028
|
-
} catch
|
|
4302
|
+
} catch {
|
|
3029
4303
|
return false;
|
|
3030
4304
|
}
|
|
3031
4305
|
}
|
|
@@ -3043,33 +4317,205 @@ function toolsDecisionFactory(opts, summarizationEnabled) {
|
|
|
3043
4317
|
};
|
|
3044
4318
|
}
|
|
3045
4319
|
|
|
4320
|
+
// src/smart/memory.ts
|
|
4321
|
+
function normalizeFact(scope, fact) {
|
|
4322
|
+
return {
|
|
4323
|
+
...fact,
|
|
4324
|
+
scope,
|
|
4325
|
+
obsolete: fact.obsolete ?? false,
|
|
4326
|
+
lastUpdatedAt: fact.lastUpdatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4327
|
+
confidence: Number.isFinite(fact.confidence) ? fact.confidence : 0.5
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
var InMemoryMemoryStore = class {
|
|
4331
|
+
constructor() {
|
|
4332
|
+
this.buckets = /* @__PURE__ */ new Map();
|
|
4333
|
+
}
|
|
4334
|
+
async get(scope, options) {
|
|
4335
|
+
const includeObsolete = options?.includeObsolete === true;
|
|
4336
|
+
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());
|
|
4337
|
+
return typeof options?.limit === "number" ? all.slice(0, options.limit) : all;
|
|
4338
|
+
}
|
|
4339
|
+
async upsert(scope, facts) {
|
|
4340
|
+
const existing = [...this.buckets.get(scope) || []];
|
|
4341
|
+
for (const fact of facts) {
|
|
4342
|
+
const normalized = normalizeFact(scope, fact);
|
|
4343
|
+
const sameKey = existing.filter((entry) => entry.key === normalized.key && !entry.obsolete);
|
|
4344
|
+
for (const entry of sameKey) {
|
|
4345
|
+
if (entry.value !== normalized.value) {
|
|
4346
|
+
entry.obsolete = true;
|
|
4347
|
+
entry.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
const match = existing.find((entry) => entry.key === normalized.key && entry.value === normalized.value && !entry.obsolete);
|
|
4351
|
+
if (match) {
|
|
4352
|
+
match.confidence = Math.max(match.confidence, normalized.confidence);
|
|
4353
|
+
match.sourceTurn = normalized.sourceTurn;
|
|
4354
|
+
match.ttl = normalized.ttl;
|
|
4355
|
+
match.tags = normalized.tags;
|
|
4356
|
+
match.lastUpdatedAt = normalized.lastUpdatedAt;
|
|
4357
|
+
} else {
|
|
4358
|
+
existing.push(normalized);
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4361
|
+
this.buckets.set(scope, existing);
|
|
4362
|
+
}
|
|
4363
|
+
async markObsolete(scope, keys) {
|
|
4364
|
+
const bucket = this.buckets.get(scope) || [];
|
|
4365
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4366
|
+
for (const fact of bucket) {
|
|
4367
|
+
if (keys.includes(fact.key)) {
|
|
4368
|
+
fact.obsolete = true;
|
|
4369
|
+
fact.lastUpdatedAt = now;
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
this.buckets.set(scope, bucket);
|
|
4373
|
+
}
|
|
4374
|
+
async semanticSearch(scope, query, options) {
|
|
4375
|
+
const normalizedQuery = query.toLowerCase();
|
|
4376
|
+
const bucket = (this.buckets.get(scope) || []).filter((fact) => !fact.obsolete);
|
|
4377
|
+
const scored = bucket.map((fact) => {
|
|
4378
|
+
const haystack = `${fact.key} ${fact.value} ${(fact.tags || []).join(" ")}`.toLowerCase();
|
|
4379
|
+
const score = haystack.includes(normalizedQuery) ? 2 : normalizedQuery.split(/\s+/).filter(Boolean).reduce((acc, token) => acc + (haystack.includes(token) ? 1 : 0), 0);
|
|
4380
|
+
return { fact, score };
|
|
4381
|
+
}).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || right.fact.confidence - left.fact.confidence).map((entry) => entry.fact);
|
|
4382
|
+
return typeof options?.limit === "number" ? scored.slice(0, options.limit) : scored;
|
|
4383
|
+
}
|
|
4384
|
+
};
|
|
4385
|
+
function resolveMemoryStore(config) {
|
|
4386
|
+
if (config.memory.store) return config.memory.store;
|
|
4387
|
+
if (config.memory.provider === "inMemory") return new InMemoryMemoryStore();
|
|
4388
|
+
return void 0;
|
|
4389
|
+
}
|
|
4390
|
+
async function readMemoryFacts(store, config, query) {
|
|
4391
|
+
if (!store) return [];
|
|
4392
|
+
const scope = config.memory.scope;
|
|
4393
|
+
const limit = config.context.lastTurnsToKeep;
|
|
4394
|
+
const policy = config.memory.readPolicy;
|
|
4395
|
+
if (policy === "recent_only") {
|
|
4396
|
+
return store.get(scope, { limit });
|
|
4397
|
+
}
|
|
4398
|
+
if (policy === "semantic" && typeof store.semanticSearch === "function") {
|
|
4399
|
+
return store.semanticSearch(scope, query, { limit });
|
|
4400
|
+
}
|
|
4401
|
+
if (policy === "hybrid") {
|
|
4402
|
+
const recent = await store.get(scope, { limit: Math.max(3, Math.ceil(limit / 2)) });
|
|
4403
|
+
const semantic = typeof store.semanticSearch === "function" ? await store.semanticSearch(scope, query, { limit: Math.max(3, Math.ceil(limit / 2)) }) : [];
|
|
4404
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
4405
|
+
for (const fact of [...semantic, ...recent]) {
|
|
4406
|
+
deduped.set(`${fact.key}:${fact.value}`, fact);
|
|
4407
|
+
}
|
|
4408
|
+
return [...deduped.values()].slice(0, limit);
|
|
4409
|
+
}
|
|
4410
|
+
return store.get(scope, { limit });
|
|
4411
|
+
}
|
|
4412
|
+
async function writeSummaryFactsToMemory(store, config, summary, sourceTurn) {
|
|
4413
|
+
if (!store || !summary) return [];
|
|
4414
|
+
const facts = summary.stable_facts.map((fact) => ({
|
|
4415
|
+
key: fact.key,
|
|
4416
|
+
value: fact.value,
|
|
4417
|
+
sourceTurn,
|
|
4418
|
+
confidence: fact.confidence ?? 0.8,
|
|
4419
|
+
obsolete: false,
|
|
4420
|
+
tags: ["summary_fact"]
|
|
4421
|
+
}));
|
|
4422
|
+
if (summary.discarded_obsolete.length > 0) {
|
|
4423
|
+
await store.markObsolete(config.memory.scope, summary.discarded_obsolete);
|
|
4424
|
+
}
|
|
4425
|
+
if (facts.length > 0) {
|
|
4426
|
+
await store.upsert(config.memory.scope, facts);
|
|
4427
|
+
}
|
|
4428
|
+
return store.get(config.memory.scope, { limit: config.context.lastTurnsToKeep + summary.stable_facts.length });
|
|
4429
|
+
}
|
|
4430
|
+
function renderMemoryFacts(facts) {
|
|
4431
|
+
if (facts.length === 0) return "";
|
|
4432
|
+
return [
|
|
4433
|
+
"Memory facts:",
|
|
4434
|
+
...facts.map((fact) => `- ${fact.key}: ${fact.value} (confidence ${fact.confidence.toFixed(2)})`)
|
|
4435
|
+
].join("\n");
|
|
4436
|
+
}
|
|
4437
|
+
|
|
3046
4438
|
// src/smart/index.ts
|
|
3047
4439
|
function createSmartAgent(opts) {
|
|
3048
|
-
const
|
|
3049
|
-
const planningEnabled =
|
|
4440
|
+
const resolved = normalizeSmartAgentOptions(opts);
|
|
4441
|
+
const planningEnabled = resolved.planning.mode !== "off";
|
|
4442
|
+
const memoryStore = resolveMemoryStore(resolved);
|
|
4443
|
+
const runtimeOpts = {
|
|
4444
|
+
...opts,
|
|
4445
|
+
runtimeProfile: resolved.runtimeProfile,
|
|
4446
|
+
limits: resolved.limits,
|
|
4447
|
+
summarization: resolved.summarization,
|
|
4448
|
+
context: resolved.context,
|
|
4449
|
+
planning: resolved.planning,
|
|
4450
|
+
delegation: resolved.delegation,
|
|
4451
|
+
memory: { ...resolved.memory, store: memoryStore },
|
|
4452
|
+
toolResponses: resolved.toolResponses,
|
|
4453
|
+
watchdog: resolved.watchdog,
|
|
4454
|
+
useTodoList: planningEnabled
|
|
4455
|
+
};
|
|
4456
|
+
const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
|
|
3050
4457
|
const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
|
|
3051
4458
|
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);
|
|
4459
|
+
const base = createAgent({ ...runtimeOpts, tools: mergedTools });
|
|
4460
|
+
base.__runtime.runtimeProfile = resolved.runtimeProfile;
|
|
4461
|
+
base.__runtime.smart = resolved;
|
|
4462
|
+
const summarizationEnabled = resolved.summarization.enable !== false;
|
|
4463
|
+
const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
|
|
4464
|
+
const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
|
|
4465
|
+
const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
|
|
3062
4466
|
const structuredOutputHint = opts.outputSchema ? [
|
|
3063
4467
|
"A structured output schema is active.",
|
|
3064
4468
|
"Do NOT output the final JSON directly as an assistant message.",
|
|
3065
4469
|
"When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
|
|
3066
4470
|
"Call it exactly once then STOP producing further assistant messages."
|
|
3067
4471
|
].join("\n") : "";
|
|
4472
|
+
const runtimeHint = [
|
|
4473
|
+
resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
|
|
4474
|
+
`Context policy: ${resolved.context.policy}.`,
|
|
4475
|
+
`Planning mode: ${resolved.planning.mode}. Replan policy: ${resolved.planning.replanPolicy}.`,
|
|
4476
|
+
`Delegation mode: ${resolved.delegation.mode}. Child context policy: ${resolved.delegation.childContextPolicy}.`,
|
|
4477
|
+
`Tool response policy: ${resolved.context.toolResponsePolicy}.`
|
|
4478
|
+
].join("\n");
|
|
4479
|
+
function latestUserPrompt(messages) {
|
|
4480
|
+
const latest = [...messages].reverse().find((message) => message.role === "user");
|
|
4481
|
+
if (!latest) return "";
|
|
4482
|
+
return typeof latest.content === "string" ? latest.content : Array.isArray(latest.content) ? latest.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join(" ") : "";
|
|
4483
|
+
}
|
|
4484
|
+
async function syncMemory(state) {
|
|
4485
|
+
const query = latestUserPrompt(state.messages || []);
|
|
4486
|
+
const memoryFacts = await readMemoryFacts(memoryStore, resolved, query);
|
|
4487
|
+
return { ...state, memoryFacts };
|
|
4488
|
+
}
|
|
4489
|
+
async function persistLatestSummary(state) {
|
|
4490
|
+
if (!memoryStore) return state;
|
|
4491
|
+
if (resolved.memory.writePolicy === "manual") return state;
|
|
4492
|
+
const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
|
|
4493
|
+
if (!latestSummary) return state;
|
|
4494
|
+
if (resolved.memory.writePolicy === "auto_important" && latestSummary.stable_facts.length === 0) {
|
|
4495
|
+
return state;
|
|
4496
|
+
}
|
|
4497
|
+
const memoryFacts = await writeSummaryFactsToMemory(memoryStore, resolved, latestSummary, Math.max(1, (state.messages || []).filter((message) => message.role === "user").length));
|
|
4498
|
+
return { ...state, memoryFacts };
|
|
4499
|
+
}
|
|
4500
|
+
function syncPlanState(state) {
|
|
4501
|
+
if (!Array.isArray(stateRef.todoList)) return state;
|
|
4502
|
+
return {
|
|
4503
|
+
...state,
|
|
4504
|
+
plan: {
|
|
4505
|
+
version: stateRef.planVersion || state.planVersion || 1,
|
|
4506
|
+
steps: stateRef.todoList,
|
|
4507
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4508
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4509
|
+
},
|
|
4510
|
+
planVersion: stateRef.planVersion || state.planVersion || 1
|
|
4511
|
+
};
|
|
4512
|
+
}
|
|
3068
4513
|
function systemMessage2() {
|
|
3069
4514
|
const sys = buildSystemPrompt(
|
|
3070
|
-
[opts.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
|
|
3071
|
-
|
|
3072
|
-
opts.name || "Agent"
|
|
4515
|
+
[opts.systemPrompt, runtimeHint, structuredOutputHint].filter(Boolean).join("\n"),
|
|
4516
|
+
planningEnabled,
|
|
4517
|
+
opts.name || "Agent",
|
|
4518
|
+
opts.todoListPrompt
|
|
3073
4519
|
);
|
|
3074
4520
|
return { role: "system", content: sys };
|
|
3075
4521
|
}
|
|
@@ -3077,38 +4523,81 @@ function createSmartAgent(opts) {
|
|
|
3077
4523
|
invoke: async (input, config) => {
|
|
3078
4524
|
stateRef.toolHistory = input.toolHistory;
|
|
3079
4525
|
stateRef.toolHistoryArchived = input.toolHistoryArchived;
|
|
4526
|
+
stateRef.todoList = input.plan?.steps;
|
|
4527
|
+
stateRef.planVersion = input.planVersion || input.plan?.version || 0;
|
|
4528
|
+
stateRef.adherenceScore = input.plan?.adherenceScore || 0;
|
|
3080
4529
|
const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
|
|
3081
4530
|
const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
|
|
3082
|
-
let state = { ...input, messages: seedMessages };
|
|
4531
|
+
let state = await syncMemory({ ...input, messages: seedMessages });
|
|
3083
4532
|
let lastResult = null;
|
|
3084
|
-
|
|
4533
|
+
let rawMessages = [...seedMessages];
|
|
4534
|
+
const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
|
|
3085
4535
|
const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
|
|
4536
|
+
let previousTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
|
|
3086
4537
|
for (let i = 0; i < iterationLimit; i++) {
|
|
4538
|
+
state = { ...state, messages: rawMessages };
|
|
3087
4539
|
const next = summarizationEnabled ? decideBefore(state) : "agent";
|
|
3088
4540
|
if (next === "contextSummarize" && summarizer) {
|
|
3089
4541
|
const delta = await summarizer(state);
|
|
3090
4542
|
if (!delta || Object.keys(delta).length === 0) ; else {
|
|
3091
|
-
state = { ...state, ...delta };
|
|
4543
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4544
|
+
rawMessages = [...state.messages || rawMessages];
|
|
3092
4545
|
continue;
|
|
3093
4546
|
}
|
|
3094
4547
|
}
|
|
3095
|
-
const
|
|
4548
|
+
const modelMessages = buildModelMessages(state, resolved);
|
|
4549
|
+
const res = await base.invoke({ ...state, messages: modelMessages }, config);
|
|
3096
4550
|
lastResult = res;
|
|
3097
4551
|
const currentSummaries = state.summaries;
|
|
4552
|
+
const currentSummaryRecords = state.summaryRecords;
|
|
4553
|
+
const currentPlan = state.plan;
|
|
4554
|
+
const appendedMessages = res.messages.slice(modelMessages.length);
|
|
4555
|
+
rawMessages = [...rawMessages, ...appendedMessages];
|
|
3098
4556
|
state = res.state || { ...state, messages: res.messages };
|
|
4557
|
+
state = { ...state, messages: rawMessages };
|
|
3099
4558
|
if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
|
|
3100
4559
|
state = { ...state, summaries: currentSummaries };
|
|
3101
4560
|
}
|
|
3102
|
-
if (state.
|
|
4561
|
+
if (currentSummaryRecords && currentSummaryRecords.length > 0 && (!state.summaryRecords || state.summaryRecords.length === 0)) {
|
|
4562
|
+
state = { ...state, summaryRecords: currentSummaryRecords };
|
|
4563
|
+
}
|
|
4564
|
+
if (currentPlan && !state.plan) {
|
|
4565
|
+
state = { ...state, plan: currentPlan };
|
|
4566
|
+
}
|
|
4567
|
+
state = syncPlanState(await syncMemory(state));
|
|
4568
|
+
stateRef.toolHistory = state.toolHistory;
|
|
4569
|
+
stateRef.toolHistoryArchived = state.toolHistoryArchived;
|
|
4570
|
+
const currentTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
|
|
4571
|
+
const tokenDrift = Math.max(0, currentTokenCount - previousTokenCount);
|
|
4572
|
+
const toolMessagesThisTurn = appendedMessages.filter((message) => message.role === "tool").length;
|
|
4573
|
+
const overToolingRate = appendedMessages.length === 0 ? 0 : Number((toolMessagesThisTurn / appendedMessages.length).toFixed(2));
|
|
4574
|
+
state = {
|
|
4575
|
+
...state,
|
|
4576
|
+
watchdog: {
|
|
4577
|
+
tokenDrift,
|
|
4578
|
+
contextRotScore: estimateContextRotScore(state),
|
|
4579
|
+
overToolingRate,
|
|
4580
|
+
compactions: state.watchdog?.compactions || 0,
|
|
4581
|
+
lastAction: appendedMessages.length > 0 ? "agent_turn" : state.watchdog?.lastAction
|
|
4582
|
+
}
|
|
4583
|
+
};
|
|
4584
|
+
previousTokenCount = currentTokenCount;
|
|
4585
|
+
if (resolved.watchdog.enabled && resolved.watchdog.autoCompaction) {
|
|
4586
|
+
const shouldCompact = tokenDrift >= resolved.watchdog.tokenDriftThreshold || (state.watchdog?.overToolingRate || 0) >= resolved.watchdog.overToolingSpikeThreshold || (state.watchdog?.contextRotScore || 0) >= resolved.watchdog.contextRotThreshold;
|
|
4587
|
+
if (shouldCompact) {
|
|
4588
|
+
state = { ...state, ctx: { ...state.ctx || {}, __needsSummarization: true } };
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
3103
4591
|
if (state.ctx?.__needsSummarization && summarizer) {
|
|
3104
4592
|
const ctx = { ...state.ctx || {} };
|
|
3105
4593
|
delete ctx.__needsSummarization;
|
|
3106
4594
|
state = { ...state, ctx };
|
|
3107
4595
|
const delta = await summarizer(state);
|
|
3108
4596
|
if (delta && Object.keys(delta).length > 0) {
|
|
3109
|
-
state = { ...state, ...delta };
|
|
4597
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4598
|
+
rawMessages = [...state.messages || rawMessages];
|
|
4599
|
+
continue;
|
|
3110
4600
|
}
|
|
3111
|
-
continue;
|
|
3112
4601
|
}
|
|
3113
4602
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
3114
4603
|
if (summarizationEnabled) {
|
|
@@ -3116,7 +4605,8 @@ function createSmartAgent(opts) {
|
|
|
3116
4605
|
if (after === "contextSummarize" && summarizer) {
|
|
3117
4606
|
const delta = await summarizer(state);
|
|
3118
4607
|
if (delta && Object.keys(delta).length > 0) {
|
|
3119
|
-
state = { ...state, ...delta };
|
|
4608
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4609
|
+
rawMessages = [...state.messages || rawMessages];
|
|
3120
4610
|
continue;
|
|
3121
4611
|
}
|
|
3122
4612
|
}
|
|
@@ -3129,10 +4619,12 @@ function createSmartAgent(opts) {
|
|
|
3129
4619
|
}
|
|
3130
4620
|
if (state.summaries && state.summaries.length > 0) {
|
|
3131
4621
|
if (lastResult.state) {
|
|
3132
|
-
lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
|
|
4622
|
+
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
4623
|
} else {
|
|
3134
|
-
lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
|
|
4624
|
+
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
4625
|
}
|
|
4626
|
+
} else if (lastResult.state) {
|
|
4627
|
+
lastResult = { ...lastResult, state: { ...lastResult.state, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
|
|
3136
4628
|
}
|
|
3137
4629
|
return lastResult;
|
|
3138
4630
|
},
|
|
@@ -3146,6 +4638,141 @@ function createSmartAgent(opts) {
|
|
|
3146
4638
|
return instance;
|
|
3147
4639
|
}
|
|
3148
4640
|
|
|
4641
|
+
// src/smart/eval.ts
|
|
4642
|
+
function normalizeProfileTarget(target) {
|
|
4643
|
+
if (typeof target === "string") {
|
|
4644
|
+
return {
|
|
4645
|
+
label: target,
|
|
4646
|
+
runtimeProfile: target,
|
|
4647
|
+
baseProfile: target === "custom" ? "balanced" : target
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
return {
|
|
4651
|
+
label: target.label,
|
|
4652
|
+
runtimeProfile: target.runtimeProfile,
|
|
4653
|
+
baseProfile: target.baseProfile ?? (target.runtimeProfile === "custom" ? target.customProfile?.extends ?? "balanced" : target.runtimeProfile),
|
|
4654
|
+
customProfile: target.customProfile
|
|
4655
|
+
};
|
|
4656
|
+
}
|
|
4657
|
+
function normalizeText(value) {
|
|
4658
|
+
return (value || "").toLowerCase();
|
|
4659
|
+
}
|
|
4660
|
+
function ratio(numerator, denominator) {
|
|
4661
|
+
if (denominator <= 0) return 1;
|
|
4662
|
+
return Number((numerator / denominator).toFixed(3));
|
|
4663
|
+
}
|
|
4664
|
+
function sum(values) {
|
|
4665
|
+
return values.reduce((acc, value) => acc + value, 0);
|
|
4666
|
+
}
|
|
4667
|
+
function mean(values) {
|
|
4668
|
+
if (values.length === 0) return 0;
|
|
4669
|
+
return Number((sum(values) / values.length).toFixed(3));
|
|
4670
|
+
}
|
|
4671
|
+
function extractTotalTokens(usage) {
|
|
4672
|
+
if (!usage?.totals || typeof usage.totals !== "object") return 0;
|
|
4673
|
+
return Object.values(usage.totals).reduce((acc, entry) => acc + (Number(entry?.total) || 0), 0);
|
|
4674
|
+
}
|
|
4675
|
+
function caseResultFromRun(descriptor, testCase, content, events, state, latencyMs, totalTokens) {
|
|
4676
|
+
const normalizedContent = normalizeText(content);
|
|
4677
|
+
const matchedPhrases = (testCase.expectedPhrases || []).filter((phrase) => normalizedContent.includes(normalizeText(phrase)));
|
|
4678
|
+
const matchedFacts = (testCase.expectedFacts || []).filter((fact) => {
|
|
4679
|
+
const memoryMatch = state?.memoryFacts?.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value)));
|
|
4680
|
+
const summaryMatch = state?.summaryRecords?.some((record) => record.stable_facts.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value))));
|
|
4681
|
+
return Boolean(memoryMatch || summaryMatch || normalizedContent.includes(normalizeText(fact.value)));
|
|
4682
|
+
});
|
|
4683
|
+
const forbiddenMisses = (testCase.forbiddenPhrases || []).filter((phrase) => !normalizedContent.includes(normalizeText(phrase)));
|
|
4684
|
+
const toolCalls = events.filter((event) => event.type === "tool_call" && event.phase === "success");
|
|
4685
|
+
const expectedToolHits = (testCase.expectedToolNames || []).filter((toolName) => toolCalls.some((event) => event.name === toolName));
|
|
4686
|
+
const toolPrecision = ratio(expectedToolHits.length, Math.max(toolCalls.length, 1));
|
|
4687
|
+
const recallAccuracy = ratio(matchedPhrases.length + matchedFacts.length, (testCase.expectedPhrases?.length || 0) + (testCase.expectedFacts?.length || 0));
|
|
4688
|
+
const obsoleteDropAccuracy = ratio(forbiddenMisses.length, testCase.forbiddenPhrases?.length || 0);
|
|
4689
|
+
const trajectoryScore = ratio(expectedToolHits.length, testCase.expectedToolNames?.length || 0);
|
|
4690
|
+
const hadRecoverableError = events.some((event) => event.type === "tool_call" && event.phase === "error");
|
|
4691
|
+
const recoveryRate = hadRecoverableError ? Number((normalizedContent.length > 0 ? 1 : 0).toFixed(3)) : 1;
|
|
4692
|
+
const overToolingRate = toolCalls.length === 0 ? 0 : Number((Math.max(0, toolCalls.length - Math.max(testCase.expectedToolNames?.length || 0, 1)) / toolCalls.length).toFixed(3));
|
|
4693
|
+
const success = recallAccuracy >= 0.8 && obsoleteDropAccuracy >= 0.8;
|
|
4694
|
+
return {
|
|
4695
|
+
id: testCase.id,
|
|
4696
|
+
family: testCase.family,
|
|
4697
|
+
success,
|
|
4698
|
+
recallAccuracy,
|
|
4699
|
+
obsoleteDropAccuracy,
|
|
4700
|
+
trajectoryScore: Number(((trajectoryScore + toolPrecision) / 2).toFixed(3)),
|
|
4701
|
+
recoveryRate,
|
|
4702
|
+
overToolingRate,
|
|
4703
|
+
latencyMs,
|
|
4704
|
+
totalTokens,
|
|
4705
|
+
profile: descriptor.runtimeProfile,
|
|
4706
|
+
profileLabel: descriptor.label,
|
|
4707
|
+
baseProfile: descriptor.baseProfile,
|
|
4708
|
+
notes: success ? [] : [
|
|
4709
|
+
recallAccuracy < 0.8 ? "Recall below threshold." : "",
|
|
4710
|
+
obsoleteDropAccuracy < 0.8 ? "Obsolete filtering below threshold." : "",
|
|
4711
|
+
trajectoryScore < 0.5 ? "Tool trajectory diverged from expectation." : ""
|
|
4712
|
+
].filter(Boolean)
|
|
4713
|
+
};
|
|
4714
|
+
}
|
|
4715
|
+
function aggregateMetrics(caseResults) {
|
|
4716
|
+
const taskSuccess = ratio(caseResults.filter((result) => result.success).length, caseResults.length);
|
|
4717
|
+
const recallAccuracy = mean(caseResults.map((result) => result.recallAccuracy));
|
|
4718
|
+
const obsoleteDropAccuracy = mean(caseResults.map((result) => result.obsoleteDropAccuracy));
|
|
4719
|
+
const trajectoryScore = mean(caseResults.map((result) => result.trajectoryScore));
|
|
4720
|
+
const recoveryRate = mean(caseResults.map((result) => result.recoveryRate));
|
|
4721
|
+
const overToolingRate = mean(caseResults.map((result) => result.overToolingRate));
|
|
4722
|
+
const latencyMs = mean(caseResults.map((result) => result.latencyMs));
|
|
4723
|
+
const totalTokens = sum(caseResults.map((result) => result.totalTokens || 0));
|
|
4724
|
+
const toolPrecision = trajectoryScore;
|
|
4725
|
+
const costScore = totalTokens <= 0 ? 1 : Number((1 / (1 + totalTokens / 1e4)).toFixed(3));
|
|
4726
|
+
const score = Number((0.45 * taskSuccess + 0.2 * trajectoryScore + 0.15 * toolPrecision + 0.1 * recoveryRate + 0.1 * costScore).toFixed(3));
|
|
4727
|
+
return {
|
|
4728
|
+
taskSuccess,
|
|
4729
|
+
recallAccuracy,
|
|
4730
|
+
obsoleteDropAccuracy,
|
|
4731
|
+
trajectoryScore,
|
|
4732
|
+
recoveryRate,
|
|
4733
|
+
overToolingRate,
|
|
4734
|
+
latencyMs,
|
|
4735
|
+
totalTokens,
|
|
4736
|
+
score
|
|
4737
|
+
};
|
|
4738
|
+
}
|
|
4739
|
+
async function runSmartAgentEvalHarness(params) {
|
|
4740
|
+
const results = [];
|
|
4741
|
+
for (const profileTarget of params.profiles) {
|
|
4742
|
+
const descriptor = normalizeProfileTarget(profileTarget);
|
|
4743
|
+
const caseResults = [];
|
|
4744
|
+
for (const testCase of params.cases) {
|
|
4745
|
+
const agent = params.createAgent(descriptor.runtimeProfile, descriptor);
|
|
4746
|
+
const events = [];
|
|
4747
|
+
const startedAt = Date.now();
|
|
4748
|
+
const result = await agent.invoke({
|
|
4749
|
+
messages: [{ role: "user", content: testCase.prompt }]
|
|
4750
|
+
}, {
|
|
4751
|
+
onEvent: (event) => {
|
|
4752
|
+
events.push(event);
|
|
4753
|
+
}
|
|
4754
|
+
});
|
|
4755
|
+
caseResults.push(caseResultFromRun(
|
|
4756
|
+
descriptor,
|
|
4757
|
+
testCase,
|
|
4758
|
+
result.content,
|
|
4759
|
+
events,
|
|
4760
|
+
result.state,
|
|
4761
|
+
Date.now() - startedAt,
|
|
4762
|
+
extractTotalTokens(result.metadata?.usage)
|
|
4763
|
+
));
|
|
4764
|
+
}
|
|
4765
|
+
results.push({
|
|
4766
|
+
profile: descriptor.runtimeProfile,
|
|
4767
|
+
profileLabel: descriptor.label,
|
|
4768
|
+
baseProfile: descriptor.baseProfile,
|
|
4769
|
+
metrics: aggregateMetrics(caseResults),
|
|
4770
|
+
cases: caseResults
|
|
4771
|
+
});
|
|
4772
|
+
}
|
|
4773
|
+
return results;
|
|
4774
|
+
}
|
|
4775
|
+
|
|
3149
4776
|
// src/utils/content.ts
|
|
3150
4777
|
function contentToString(content) {
|
|
3151
4778
|
try {
|
|
@@ -3423,7 +5050,9 @@ function createCodeGuardrail(options) {
|
|
|
3423
5050
|
});
|
|
3424
5051
|
}
|
|
3425
5052
|
|
|
5053
|
+
exports.DEFAULT_PROFILE_CONFIGS = DEFAULT_PROFILE_CONFIGS;
|
|
3426
5054
|
exports.GuardrailPhase = GuardrailPhase;
|
|
5055
|
+
exports.InMemoryMemoryStore = InMemoryMemoryStore;
|
|
3427
5056
|
exports.agentVerdictRule = agentVerdictRule;
|
|
3428
5057
|
exports.applyTokenLimits = applyTokenLimits;
|
|
3429
5058
|
exports.buildSystemPrompt = buildSystemPrompt;
|
|
@@ -3451,13 +5080,24 @@ exports.evaluateGuardrails = evaluateGuardrails;
|
|
|
3451
5080
|
exports.fileSink = fileSink;
|
|
3452
5081
|
exports.fromLangchainModel = fromLangchainModel;
|
|
3453
5082
|
exports.fromLangchainTools = fromLangchainTools;
|
|
5083
|
+
exports.generateSpanId = generateSpanId;
|
|
5084
|
+
exports.generateTraceId = generateTraceId;
|
|
5085
|
+
exports.getResolvedSmartConfig = getResolvedSmartConfig;
|
|
3454
5086
|
exports.httpSink = httpSink;
|
|
3455
5087
|
exports.isSmartModel = isSmartModel;
|
|
3456
5088
|
exports.jsonSchemaRule = jsonSchemaRule;
|
|
5089
|
+
exports.normalizeSmartAgentOptions = normalizeSmartAgentOptions;
|
|
5090
|
+
exports.otlpSink = otlpSink;
|
|
5091
|
+
exports.readMemoryFacts = readMemoryFacts;
|
|
3457
5092
|
exports.regexRule = regexRule;
|
|
5093
|
+
exports.renderMemoryFacts = renderMemoryFacts;
|
|
5094
|
+
exports.resolveMemoryStore = resolveMemoryStore;
|
|
3458
5095
|
exports.resolveToolApprovalState = resolveToolApprovalState;
|
|
3459
5096
|
exports.restoreSnapshot = restoreSnapshot;
|
|
5097
|
+
exports.runSmartAgentEvalHarness = runSmartAgentEvalHarness;
|
|
3460
5098
|
exports.startStreamingSession = startStreamingSession;
|
|
5099
|
+
exports.traceSessionToOtlp = traceSessionToOtlp;
|
|
3461
5100
|
exports.withTools = withTools;
|
|
5101
|
+
exports.writeSummaryFactsToMemory = writeSummaryFactsToMemory;
|
|
3462
5102
|
//# sourceMappingURL=index.cjs.map
|
|
3463
5103
|
//# sourceMappingURL=index.cjs.map
|