@cognipeer/agent-sdk 0.3.0 → 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 +1939 -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 +1927 -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
|
};
|
|
@@ -1927,7 +2774,15 @@ function createGuardrail(options) {
|
|
|
1927
2774
|
}
|
|
1928
2775
|
|
|
1929
2776
|
// src/utils/stateSnapshot.ts
|
|
1930
|
-
var DISALLOWED_CTX_KEYS = /* @__PURE__ */ new Set([
|
|
2777
|
+
var DISALLOWED_CTX_KEYS = /* @__PURE__ */ new Set([
|
|
2778
|
+
"__onEvent",
|
|
2779
|
+
"__onProgress",
|
|
2780
|
+
"__onStream",
|
|
2781
|
+
"__traceSession",
|
|
2782
|
+
"__paused",
|
|
2783
|
+
"__cancellationToken",
|
|
2784
|
+
"__abortSignal"
|
|
2785
|
+
]);
|
|
1931
2786
|
var clone = (value) => {
|
|
1932
2787
|
if (typeof globalThis.structuredClone === "function") {
|
|
1933
2788
|
return globalThis.structuredClone(value);
|
|
@@ -2019,68 +2874,20 @@ function resolveToolApprovalState(state, resolution) {
|
|
|
2019
2874
|
};
|
|
2020
2875
|
}
|
|
2021
2876
|
|
|
2022
|
-
// src/
|
|
2023
|
-
function
|
|
2024
|
-
if (!
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
|
|
2028
|
-
}
|
|
2029
|
-
function extractMessageText(m) {
|
|
2030
|
-
if (!m) return "";
|
|
2031
|
-
const parts = [];
|
|
2032
|
-
parts.push(m.role || "");
|
|
2033
|
-
if (m.name) parts.push(m.name);
|
|
2034
|
-
if (typeof m.content === "string") {
|
|
2035
|
-
parts.push(m.content);
|
|
2036
|
-
} else if (Array.isArray(m.content)) {
|
|
2037
|
-
for (const c of m.content) {
|
|
2038
|
-
if (typeof c === "string") parts.push(c);
|
|
2039
|
-
else if (c?.text) parts.push(c.text);
|
|
2040
|
-
else if (c?.content) parts.push(c.content);
|
|
2041
|
-
else if (c?.type === "tool_use" && c?.input) {
|
|
2042
|
-
parts.push(c.name || "");
|
|
2043
|
-
parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
|
|
2044
|
-
} else if (c != null && typeof c === "object") {
|
|
2045
|
-
parts.push(JSON.stringify(c));
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
} else if (m.content != null && typeof m.content === "object") {
|
|
2049
|
-
parts.push(JSON.stringify(m.content));
|
|
2050
|
-
}
|
|
2051
|
-
if (Array.isArray(m.tool_calls)) {
|
|
2052
|
-
for (const tc of m.tool_calls) {
|
|
2053
|
-
if (tc?.function?.name) parts.push(tc.function.name);
|
|
2054
|
-
if (tc?.function?.arguments) {
|
|
2055
|
-
parts.push(
|
|
2056
|
-
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2057
|
-
);
|
|
2058
|
-
}
|
|
2059
|
-
if (tc?.name && !tc?.function?.name) parts.push(tc.name);
|
|
2060
|
-
if (tc?.args && !tc?.function?.arguments) {
|
|
2061
|
-
parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
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;
|
|
2064
2882
|
}
|
|
2065
|
-
if (Array.isArray(
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2071
|
-
);
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
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
|
+
});
|
|
2074
2888
|
}
|
|
2075
|
-
return
|
|
2076
|
-
}
|
|
2077
|
-
function countMessagesTokens(messages) {
|
|
2078
|
-
if (!Array.isArray(messages) || messages.length === 0) return 0;
|
|
2079
|
-
const allText = messages.map(extractMessageText).join("\n");
|
|
2080
|
-
return countApproxTokens(allText) + messages.length * 4;
|
|
2889
|
+
return false;
|
|
2081
2890
|
}
|
|
2082
|
-
|
|
2083
|
-
// src/agent.ts
|
|
2084
2891
|
function createAgent(opts) {
|
|
2085
2892
|
const resolver = createResolverNode();
|
|
2086
2893
|
const agentCore = createAgentCoreNode(opts);
|
|
@@ -2123,6 +2930,7 @@ function createAgent(opts) {
|
|
|
2123
2930
|
tools: toolsBase,
|
|
2124
2931
|
guardrails: opts.guardrails,
|
|
2125
2932
|
systemPrompt: void 0,
|
|
2933
|
+
todoListPrompt: opts.todoListPrompt,
|
|
2126
2934
|
limits: opts.limits,
|
|
2127
2935
|
useTodoList: void 0,
|
|
2128
2936
|
outputSchema: opts.outputSchema,
|
|
@@ -2198,6 +3006,18 @@ function createAgent(opts) {
|
|
|
2198
3006
|
};
|
|
2199
3007
|
while (iterations < iterationLimit) {
|
|
2200
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
|
+
}
|
|
2201
3021
|
if (cancelIfRequested("loop")) break;
|
|
2202
3022
|
const skippingAgent = resumeStage === "tools";
|
|
2203
3023
|
if (!skippingAgent) {
|
|
@@ -2248,11 +3068,21 @@ function createAgent(opts) {
|
|
|
2248
3068
|
maxTok = 5e4;
|
|
2249
3069
|
}
|
|
2250
3070
|
if (maxTok) {
|
|
2251
|
-
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);
|
|
2252
3077
|
if (tokenCount > maxTok) {
|
|
2253
|
-
const
|
|
2254
|
-
|
|
2255
|
-
|
|
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
|
+
}
|
|
2256
3086
|
}
|
|
2257
3087
|
}
|
|
2258
3088
|
onProgress?.({ stage: "agent", message: "Invoking model" });
|
|
@@ -2490,7 +3320,11 @@ function createAgent(opts) {
|
|
|
2490
3320
|
schema,
|
|
2491
3321
|
func: async ({ input }) => {
|
|
2492
3322
|
const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
|
|
2493
|
-
return {
|
|
3323
|
+
return {
|
|
3324
|
+
content: res.content,
|
|
3325
|
+
output: res.output,
|
|
3326
|
+
summary: res.state?.summaries?.[res.state.summaries.length - 1]
|
|
3327
|
+
};
|
|
2494
3328
|
}
|
|
2495
3329
|
});
|
|
2496
3330
|
},
|
|
@@ -2531,6 +3365,7 @@ function createAgentNode(opts) {
|
|
|
2531
3365
|
model: opts.model,
|
|
2532
3366
|
tools: opts.tools || [],
|
|
2533
3367
|
systemPrompt: opts.systemPrompt,
|
|
3368
|
+
todoListPrompt: opts.todoListPrompt,
|
|
2534
3369
|
limits: opts.limits,
|
|
2535
3370
|
useTodoList: opts.useTodoList,
|
|
2536
3371
|
outputSchema: opts.outputSchema
|
|
@@ -2548,7 +3383,8 @@ function createAgentNode(opts) {
|
|
|
2548
3383
|
content: buildSystemPrompt(
|
|
2549
3384
|
[runtime.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
|
|
2550
3385
|
runtime.useTodoList === true,
|
|
2551
|
-
runtime.name || "Agent"
|
|
3386
|
+
runtime.name || "Agent",
|
|
3387
|
+
runtime.todoListPrompt
|
|
2552
3388
|
)
|
|
2553
3389
|
};
|
|
2554
3390
|
const messages = [systemMsg, ...state.messages];
|
|
@@ -2630,9 +3466,119 @@ function createAgentNode(opts) {
|
|
|
2630
3466
|
};
|
|
2631
3467
|
}
|
|
2632
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
|
+
|
|
2633
3567
|
// src/nodes/contextSummarize.ts
|
|
2634
3568
|
var systemMessage = (content) => ({ role: "system", content });
|
|
2635
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
|
+
}
|
|
2636
3582
|
function validateMessageSequence(messages) {
|
|
2637
3583
|
const validated = [];
|
|
2638
3584
|
const pendingToolCallIds = /* @__PURE__ */ new Set();
|
|
@@ -2658,33 +3604,137 @@ function validateMessageSequence(messages) {
|
|
|
2658
3604
|
}
|
|
2659
3605
|
return validated;
|
|
2660
3606
|
}
|
|
2661
|
-
function
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
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
|
+
}
|
|
2671
3686
|
}
|
|
2672
3687
|
return {
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
summaryPromptMaxTokens: 8e3,
|
|
2676
|
-
promptTemplate: void 0
|
|
3688
|
+
...summary,
|
|
3689
|
+
stable_facts: [...merged.values()]
|
|
2677
3690
|
};
|
|
2678
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
|
+
}
|
|
2679
3726
|
function createContextSummarizeNode(opts) {
|
|
2680
3727
|
return async (state) => {
|
|
2681
|
-
const
|
|
2682
|
-
|
|
3728
|
+
const resolved = getResolvedSmartConfig(opts, state.agent);
|
|
3729
|
+
const config = resolved.summarization;
|
|
3730
|
+
if (!config.enable) return {};
|
|
2683
3731
|
const model = opts.model;
|
|
2684
3732
|
const messages = state.messages || [];
|
|
2685
3733
|
const onEvent = state.ctx?.__onEvent;
|
|
2686
3734
|
const traceSession = state.ctx?.__traceSession;
|
|
2687
|
-
const compressableMessages = messages.filter(
|
|
3735
|
+
const compressableMessages = messages.filter(
|
|
3736
|
+
(m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
|
|
3737
|
+
);
|
|
2688
3738
|
if (compressableMessages.length === 0) {
|
|
2689
3739
|
return {};
|
|
2690
3740
|
}
|
|
@@ -2696,6 +3746,9 @@ function createContextSummarizeNode(opts) {
|
|
|
2696
3746
|
let accumulatedTokens = 0;
|
|
2697
3747
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2698
3748
|
const m = messages[i];
|
|
3749
|
+
if (isSyntheticSummaryToolMessage(m) || isSyntheticSummaryAssistantMessage(m)) {
|
|
3750
|
+
continue;
|
|
3751
|
+
}
|
|
2699
3752
|
let content = "";
|
|
2700
3753
|
if (typeof m.content === "string") content = m.content;
|
|
2701
3754
|
else if (Array.isArray(m.content)) {
|
|
@@ -2707,39 +3760,60 @@ function createContextSummarizeNode(opts) {
|
|
|
2707
3760
|
}
|
|
2708
3761
|
let prefix = `${m.role.toUpperCase()}`;
|
|
2709
3762
|
if (m.name) prefix += ` (${m.name})`;
|
|
2710
|
-
const
|
|
2711
|
-
const messageTokens = countApproxTokens(
|
|
3763
|
+
const messageText2 = `${prefix}: ${content}`;
|
|
3764
|
+
const messageTokens = countApproxTokens(messageText2);
|
|
2712
3765
|
if (accumulatedTokens + messageTokens > availableTokensForConversation) {
|
|
2713
3766
|
messageTexts.unshift("[Earlier conversation history omitted for brevity]");
|
|
2714
3767
|
break;
|
|
2715
3768
|
}
|
|
2716
|
-
messageTexts.unshift(
|
|
3769
|
+
messageTexts.unshift(messageText2);
|
|
2717
3770
|
accumulatedTokens += messageTokens;
|
|
2718
3771
|
}
|
|
2719
3772
|
const conversationText = messageTexts.join("\n\n");
|
|
3773
|
+
const canonicalFacts = extractCanonicalFacts(compressableMessages);
|
|
2720
3774
|
const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
|
|
2721
|
-
const
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
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}.
|
|
2727
3795
|
|
|
2728
|
-
Previous summary
|
|
3796
|
+
Previous summary text:
|
|
2729
3797
|
${previousSummary || "(none)"}
|
|
2730
3798
|
|
|
3799
|
+
Previous structured summary:
|
|
3800
|
+
${previousStructuredSummary ? JSON.stringify(previousStructuredSummary) : "(none)"}
|
|
3801
|
+
|
|
2731
3802
|
Conversation:
|
|
2732
3803
|
${conversationText}
|
|
2733
3804
|
|
|
2734
|
-
|
|
3805
|
+
Canonical facts extracted from tool outputs:
|
|
3806
|
+
${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fact.value}`).join("\n") : "(none)"}`;
|
|
2735
3807
|
const template = config.promptTemplate || defaultPrompt;
|
|
2736
3808
|
const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
|
|
2737
3809
|
const summaryPrompt = [
|
|
2738
|
-
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."),
|
|
2739
3811
|
humanMessage(promptBody)
|
|
2740
3812
|
];
|
|
2741
3813
|
const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
|
|
2742
3814
|
let summaryText = "Summary unavailable.";
|
|
3815
|
+
let structuredSummary;
|
|
3816
|
+
let integrity;
|
|
2743
3817
|
let outputTokensEstimate = 0;
|
|
2744
3818
|
let durationMs = 0;
|
|
2745
3819
|
let inputTokensActual;
|
|
@@ -2748,10 +3822,23 @@ Summary:`;
|
|
|
2748
3822
|
let totalTokensActual;
|
|
2749
3823
|
const startTime = Date.now();
|
|
2750
3824
|
try {
|
|
3825
|
+
if (!model || typeof model.invoke !== "function") {
|
|
3826
|
+
throw new Error("Summarization model is unavailable.");
|
|
3827
|
+
}
|
|
2751
3828
|
const response = await model.invoke(summaryPrompt);
|
|
2752
3829
|
durationMs = Date.now() - startTime;
|
|
2753
3830
|
summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
|
|
2754
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);
|
|
2755
3842
|
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
|
|
2756
3843
|
const normalized = normalizeUsage(rawUsage);
|
|
2757
3844
|
if (normalized) {
|
|
@@ -2762,17 +3849,22 @@ Summary:`;
|
|
|
2762
3849
|
}
|
|
2763
3850
|
} catch (err) {
|
|
2764
3851
|
durationMs = Date.now() - startTime;
|
|
2765
|
-
|
|
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);
|
|
2766
3856
|
const errorEvent = {
|
|
2767
3857
|
type: "summarization",
|
|
2768
|
-
summary:
|
|
2769
|
-
messagesCompressed:
|
|
3858
|
+
summary: summaryText,
|
|
3859
|
+
messagesCompressed: compressableMessages.length,
|
|
2770
3860
|
inputTokens: inputTokensEstimate,
|
|
2771
|
-
outputTokens:
|
|
3861
|
+
outputTokens: countApproxTokens(summaryText),
|
|
2772
3862
|
durationMs,
|
|
2773
3863
|
previousSummary: previousSummary || void 0,
|
|
2774
3864
|
tokenCountBefore,
|
|
2775
|
-
tokenCountAfter: tokenCountBefore
|
|
3865
|
+
tokenCountAfter: tokenCountBefore,
|
|
3866
|
+
structuredSummary,
|
|
3867
|
+
integrity
|
|
2776
3868
|
};
|
|
2777
3869
|
onEvent?.(errorEvent);
|
|
2778
3870
|
recordTraceEvent(traceSession, {
|
|
@@ -2782,11 +3874,10 @@ Summary:`;
|
|
|
2782
3874
|
status: "error",
|
|
2783
3875
|
durationMs,
|
|
2784
3876
|
inputTokens: inputTokensEstimate,
|
|
2785
|
-
outputTokens:
|
|
3877
|
+
outputTokens: countApproxTokens(summaryText),
|
|
2786
3878
|
error: { message: err?.message || String(err), stack: err?.stack },
|
|
2787
3879
|
messageList: sanitizeTracePayload(summaryPrompt)
|
|
2788
3880
|
});
|
|
2789
|
-
return {};
|
|
2790
3881
|
}
|
|
2791
3882
|
const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
|
|
2792
3883
|
messages.forEach((m, idx) => {
|
|
@@ -2800,6 +3891,9 @@ Summary:`;
|
|
|
2800
3891
|
});
|
|
2801
3892
|
const newMessages = messages.map((m) => {
|
|
2802
3893
|
if (m.role === "tool") {
|
|
3894
|
+
if (isSyntheticSummaryToolMessage(m)) {
|
|
3895
|
+
return m;
|
|
3896
|
+
}
|
|
2803
3897
|
const toolCallId = m.tool_call_id;
|
|
2804
3898
|
if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
|
|
2805
3899
|
return null;
|
|
@@ -2837,6 +3931,7 @@ Summary:`;
|
|
|
2837
3931
|
content: summaryText
|
|
2838
3932
|
};
|
|
2839
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() }];
|
|
2840
3935
|
const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
|
|
2841
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");
|
|
2842
3937
|
const tokenCountAfter = countApproxTokens(allTextAfter);
|
|
@@ -2852,8 +3947,10 @@ Summary:`;
|
|
|
2852
3947
|
previousSummary: previousSummary || void 0,
|
|
2853
3948
|
tokenCountBefore,
|
|
2854
3949
|
tokenCountAfter,
|
|
2855
|
-
archivedCount: compressableMessages.length
|
|
3950
|
+
archivedCount: compressableMessages.length,
|
|
2856
3951
|
// deprecated field for backward compatibility
|
|
3952
|
+
structuredSummary,
|
|
3953
|
+
integrity
|
|
2857
3954
|
};
|
|
2858
3955
|
onEvent?.(summarizationEvent);
|
|
2859
3956
|
recordTraceEvent(traceSession, {
|
|
@@ -2877,12 +3974,19 @@ Summary:`;
|
|
|
2877
3974
|
tokensSaved: tokenCountBefore - tokenCountAfter,
|
|
2878
3975
|
previousSummaryLength: previousSummary?.length || 0,
|
|
2879
3976
|
usageFromModel: inputTokensActual !== void 0,
|
|
2880
|
-
cachedInputTokens
|
|
3977
|
+
cachedInputTokens,
|
|
3978
|
+
integrity
|
|
2881
3979
|
}
|
|
2882
3980
|
});
|
|
2883
3981
|
return {
|
|
2884
3982
|
messages: finalMessages,
|
|
2885
|
-
summaries
|
|
3983
|
+
summaries,
|
|
3984
|
+
summaryRecords,
|
|
3985
|
+
watchdog: {
|
|
3986
|
+
...state.watchdog || {},
|
|
3987
|
+
compactions: (state.watchdog?.compactions || 0) + 1,
|
|
3988
|
+
lastAction: "summarized"
|
|
3989
|
+
}
|
|
2886
3990
|
};
|
|
2887
3991
|
};
|
|
2888
3992
|
}
|
|
@@ -2905,38 +4009,200 @@ async function applyTokenLimits({
|
|
|
2905
4009
|
}
|
|
2906
4010
|
return { ...state, messages: newMessages };
|
|
2907
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
|
+
}
|
|
2908
4074
|
function createContextTools(stateRef, opts) {
|
|
2909
4075
|
const tools = [];
|
|
2910
4076
|
if (opts?.planningEnabled) {
|
|
2911
4077
|
const manageTodo = createTool({
|
|
2912
4078
|
name: "manage_todo_list",
|
|
2913
|
-
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",
|
|
2914
4080
|
schema: zod.z.object({
|
|
2915
|
-
operation: zod.z.enum(["write", "read"]),
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
title: zod.z.string().min(1),
|
|
2919
|
-
description: zod.z.string().min(1),
|
|
2920
|
-
status: zod.z.enum(["not-started", "in-progress", "completed"]),
|
|
2921
|
-
evidence: zod.z.string().max(200).optional()
|
|
2922
|
-
})).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()
|
|
2923
4084
|
}),
|
|
2924
|
-
func: async ({ operation, todoList }) => {
|
|
4085
|
+
func: async ({ operation, expectedVersion, todoList }) => {
|
|
2925
4086
|
const onEvent = manageTodo._stateRef?.__onEvent;
|
|
4087
|
+
const currentVersion = stateRef.planVersion || 0;
|
|
2926
4088
|
if (operation === "read") {
|
|
2927
4089
|
const list = stateRef.todoList || [];
|
|
2928
|
-
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 });
|
|
2929
4091
|
return list;
|
|
2930
4092
|
}
|
|
2931
|
-
if (
|
|
2932
|
-
|
|
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
|
+
};
|
|
2933
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 || []);
|
|
2934
4198
|
const payload = {
|
|
2935
4199
|
status: "ok",
|
|
2936
4200
|
operation,
|
|
2937
|
-
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
|
|
2938
4204
|
};
|
|
2939
|
-
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 });
|
|
2940
4206
|
return payload;
|
|
2941
4207
|
}
|
|
2942
4208
|
});
|
|
@@ -2945,12 +4211,13 @@ function createContextTools(stateRef, opts) {
|
|
|
2945
4211
|
}
|
|
2946
4212
|
const getTool = createTool({
|
|
2947
4213
|
name: "get_tool_response",
|
|
2948
|
-
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.",
|
|
2949
4215
|
schema: zod.z.object({ executionId: zod.z.string().describe("Tool execution id") }),
|
|
2950
4216
|
func: async ({ executionId }) => {
|
|
2951
|
-
|
|
4217
|
+
const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
|
|
4218
|
+
let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));
|
|
2952
4219
|
if (!execution) {
|
|
2953
|
-
execution = stateRef.toolHistoryArchived?.find((t) => t
|
|
4220
|
+
execution = stateRef.toolHistoryArchived?.find((t) => matchesExecution(t));
|
|
2954
4221
|
}
|
|
2955
4222
|
if (execution) {
|
|
2956
4223
|
return execution.rawOutput || execution.output;
|
|
@@ -2981,19 +4248,33 @@ function createContextTools(stateRef, opts) {
|
|
|
2981
4248
|
}
|
|
2982
4249
|
|
|
2983
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
|
+
}
|
|
2984
4264
|
function needsSummarization(state, opts, summarizationEnabled) {
|
|
2985
4265
|
if (!summarizationEnabled) {
|
|
2986
4266
|
return false;
|
|
2987
4267
|
}
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
maxTok = opts.summarization.maxTokens;
|
|
4268
|
+
if (state.ctx?.__needsSummarization) {
|
|
4269
|
+
return true;
|
|
2991
4270
|
}
|
|
2992
|
-
|
|
2993
|
-
|
|
4271
|
+
const resolved = getResolvedSmartConfig(opts, state.agent);
|
|
4272
|
+
if (!resolved.summarization.enable) {
|
|
4273
|
+
return false;
|
|
2994
4274
|
}
|
|
4275
|
+
const maxTok = resolved.summarization.summaryTriggerTokens;
|
|
2995
4276
|
try {
|
|
2996
|
-
const allText = (state.messages || []).map((m) => {
|
|
4277
|
+
const allText = (state.messages || []).filter((message) => !isSyntheticSummaryMessage2(message)).map((m) => {
|
|
2997
4278
|
if (typeof m.content === "string") return m.content;
|
|
2998
4279
|
if (Array.isArray(m.content)) {
|
|
2999
4280
|
return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
|
|
@@ -3004,7 +4285,8 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
3004
4285
|
return "";
|
|
3005
4286
|
}).join("\n");
|
|
3006
4287
|
const tokenCount = countApproxTokens(allText);
|
|
3007
|
-
const
|
|
4288
|
+
const contextRotScore = state.watchdog?.contextRotScore || 0;
|
|
4289
|
+
const needsSum = tokenCount > maxTok || resolved.watchdog.enabled && resolved.watchdog.autoCompaction && contextRotScore >= resolved.watchdog.contextRotThreshold;
|
|
3008
4290
|
if (process.env.DEBUG_SUMMARIZATION) {
|
|
3009
4291
|
const msgCount = (state.messages || []).length;
|
|
3010
4292
|
const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
|
|
@@ -3014,10 +4296,10 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
3014
4296
|
});
|
|
3015
4297
|
console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
|
|
3016
4298
|
toolDetails.forEach((d) => console.log(` ${d}`));
|
|
3017
|
-
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}`);
|
|
3018
4300
|
}
|
|
3019
4301
|
return needsSum;
|
|
3020
|
-
} catch
|
|
4302
|
+
} catch {
|
|
3021
4303
|
return false;
|
|
3022
4304
|
}
|
|
3023
4305
|
}
|
|
@@ -3035,33 +4317,205 @@ function toolsDecisionFactory(opts, summarizationEnabled) {
|
|
|
3035
4317
|
};
|
|
3036
4318
|
}
|
|
3037
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
|
+
|
|
3038
4438
|
// src/smart/index.ts
|
|
3039
4439
|
function createSmartAgent(opts) {
|
|
3040
|
-
const
|
|
3041
|
-
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 };
|
|
3042
4457
|
const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
|
|
3043
4458
|
const mergedTools = [...opts.tools ?? [], ...contextTools];
|
|
3044
|
-
const base = createAgent({ ...
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
|
|
3052
|
-
const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
|
|
3053
|
-
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);
|
|
3054
4466
|
const structuredOutputHint = opts.outputSchema ? [
|
|
3055
4467
|
"A structured output schema is active.",
|
|
3056
4468
|
"Do NOT output the final JSON directly as an assistant message.",
|
|
3057
4469
|
"When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
|
|
3058
4470
|
"Call it exactly once then STOP producing further assistant messages."
|
|
3059
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
|
+
}
|
|
3060
4513
|
function systemMessage2() {
|
|
3061
4514
|
const sys = buildSystemPrompt(
|
|
3062
|
-
[opts.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
|
|
3063
|
-
|
|
3064
|
-
opts.name || "Agent"
|
|
4515
|
+
[opts.systemPrompt, runtimeHint, structuredOutputHint].filter(Boolean).join("\n"),
|
|
4516
|
+
planningEnabled,
|
|
4517
|
+
opts.name || "Agent",
|
|
4518
|
+
opts.todoListPrompt
|
|
3065
4519
|
);
|
|
3066
4520
|
return { role: "system", content: sys };
|
|
3067
4521
|
}
|
|
@@ -3069,37 +4523,81 @@ function createSmartAgent(opts) {
|
|
|
3069
4523
|
invoke: async (input, config) => {
|
|
3070
4524
|
stateRef.toolHistory = input.toolHistory;
|
|
3071
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;
|
|
3072
4529
|
const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
|
|
3073
4530
|
const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
|
|
3074
|
-
let state = { ...input, messages: seedMessages };
|
|
4531
|
+
let state = await syncMemory({ ...input, messages: seedMessages });
|
|
3075
4532
|
let lastResult = null;
|
|
3076
|
-
|
|
4533
|
+
let rawMessages = [...seedMessages];
|
|
4534
|
+
const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
|
|
3077
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"));
|
|
3078
4537
|
for (let i = 0; i < iterationLimit; i++) {
|
|
4538
|
+
state = { ...state, messages: rawMessages };
|
|
3079
4539
|
const next = summarizationEnabled ? decideBefore(state) : "agent";
|
|
3080
4540
|
if (next === "contextSummarize" && summarizer) {
|
|
3081
4541
|
const delta = await summarizer(state);
|
|
3082
4542
|
if (!delta || Object.keys(delta).length === 0) ; else {
|
|
3083
|
-
state = { ...state, ...delta };
|
|
4543
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4544
|
+
rawMessages = [...state.messages || rawMessages];
|
|
3084
4545
|
continue;
|
|
3085
4546
|
}
|
|
3086
4547
|
}
|
|
3087
|
-
const
|
|
4548
|
+
const modelMessages = buildModelMessages(state, resolved);
|
|
4549
|
+
const res = await base.invoke({ ...state, messages: modelMessages }, config);
|
|
3088
4550
|
lastResult = res;
|
|
3089
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];
|
|
3090
4556
|
state = res.state || { ...state, messages: res.messages };
|
|
4557
|
+
state = { ...state, messages: rawMessages };
|
|
3091
4558
|
if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
|
|
3092
4559
|
state = { ...state, summaries: currentSummaries };
|
|
3093
4560
|
}
|
|
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
|
+
}
|
|
3094
4591
|
if (state.ctx?.__needsSummarization && summarizer) {
|
|
3095
4592
|
const ctx = { ...state.ctx || {} };
|
|
3096
4593
|
delete ctx.__needsSummarization;
|
|
3097
4594
|
state = { ...state, ctx };
|
|
3098
4595
|
const delta = await summarizer(state);
|
|
3099
4596
|
if (delta && Object.keys(delta).length > 0) {
|
|
3100
|
-
state = { ...state, ...delta };
|
|
4597
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4598
|
+
rawMessages = [...state.messages || rawMessages];
|
|
4599
|
+
continue;
|
|
3101
4600
|
}
|
|
3102
|
-
continue;
|
|
3103
4601
|
}
|
|
3104
4602
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
3105
4603
|
if (summarizationEnabled) {
|
|
@@ -3107,7 +4605,8 @@ function createSmartAgent(opts) {
|
|
|
3107
4605
|
if (after === "contextSummarize" && summarizer) {
|
|
3108
4606
|
const delta = await summarizer(state);
|
|
3109
4607
|
if (delta && Object.keys(delta).length > 0) {
|
|
3110
|
-
state = { ...state, ...delta };
|
|
4608
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4609
|
+
rawMessages = [...state.messages || rawMessages];
|
|
3111
4610
|
continue;
|
|
3112
4611
|
}
|
|
3113
4612
|
}
|
|
@@ -3120,10 +4619,12 @@ function createSmartAgent(opts) {
|
|
|
3120
4619
|
}
|
|
3121
4620
|
if (state.summaries && state.summaries.length > 0) {
|
|
3122
4621
|
if (lastResult.state) {
|
|
3123
|
-
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 } };
|
|
3124
4623
|
} else {
|
|
3125
|
-
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 } };
|
|
3126
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 } };
|
|
3127
4628
|
}
|
|
3128
4629
|
return lastResult;
|
|
3129
4630
|
},
|
|
@@ -3137,6 +4638,141 @@ function createSmartAgent(opts) {
|
|
|
3137
4638
|
return instance;
|
|
3138
4639
|
}
|
|
3139
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
|
+
|
|
3140
4776
|
// src/utils/content.ts
|
|
3141
4777
|
function contentToString(content) {
|
|
3142
4778
|
try {
|
|
@@ -3414,7 +5050,9 @@ function createCodeGuardrail(options) {
|
|
|
3414
5050
|
});
|
|
3415
5051
|
}
|
|
3416
5052
|
|
|
5053
|
+
exports.DEFAULT_PROFILE_CONFIGS = DEFAULT_PROFILE_CONFIGS;
|
|
3417
5054
|
exports.GuardrailPhase = GuardrailPhase;
|
|
5055
|
+
exports.InMemoryMemoryStore = InMemoryMemoryStore;
|
|
3418
5056
|
exports.agentVerdictRule = agentVerdictRule;
|
|
3419
5057
|
exports.applyTokenLimits = applyTokenLimits;
|
|
3420
5058
|
exports.buildSystemPrompt = buildSystemPrompt;
|
|
@@ -3442,13 +5080,24 @@ exports.evaluateGuardrails = evaluateGuardrails;
|
|
|
3442
5080
|
exports.fileSink = fileSink;
|
|
3443
5081
|
exports.fromLangchainModel = fromLangchainModel;
|
|
3444
5082
|
exports.fromLangchainTools = fromLangchainTools;
|
|
5083
|
+
exports.generateSpanId = generateSpanId;
|
|
5084
|
+
exports.generateTraceId = generateTraceId;
|
|
5085
|
+
exports.getResolvedSmartConfig = getResolvedSmartConfig;
|
|
3445
5086
|
exports.httpSink = httpSink;
|
|
3446
5087
|
exports.isSmartModel = isSmartModel;
|
|
3447
5088
|
exports.jsonSchemaRule = jsonSchemaRule;
|
|
5089
|
+
exports.normalizeSmartAgentOptions = normalizeSmartAgentOptions;
|
|
5090
|
+
exports.otlpSink = otlpSink;
|
|
5091
|
+
exports.readMemoryFacts = readMemoryFacts;
|
|
3448
5092
|
exports.regexRule = regexRule;
|
|
5093
|
+
exports.renderMemoryFacts = renderMemoryFacts;
|
|
5094
|
+
exports.resolveMemoryStore = resolveMemoryStore;
|
|
3449
5095
|
exports.resolveToolApprovalState = resolveToolApprovalState;
|
|
3450
5096
|
exports.restoreSnapshot = restoreSnapshot;
|
|
5097
|
+
exports.runSmartAgentEvalHarness = runSmartAgentEvalHarness;
|
|
3451
5098
|
exports.startStreamingSession = startStreamingSession;
|
|
5099
|
+
exports.traceSessionToOtlp = traceSessionToOtlp;
|
|
3452
5100
|
exports.withTools = withTools;
|
|
5101
|
+
exports.writeSummaryFactsToMemory = writeSummaryFactsToMemory;
|
|
3453
5102
|
//# sourceMappingURL=index.cjs.map
|
|
3454
5103
|
//# sourceMappingURL=index.cjs.map
|