@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.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
1
|
+
import { z, ZodError } from 'zod';
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { Buffer } from 'buffer';
|
|
6
6
|
import { nanoid } from 'nanoid';
|
|
7
|
+
import crypto from 'crypto';
|
|
7
8
|
import AjvModule from 'ajv';
|
|
8
9
|
|
|
9
10
|
// src/adapters/langchain.ts
|
|
@@ -215,7 +216,8 @@ function createTool({
|
|
|
215
216
|
func,
|
|
216
217
|
needsApproval,
|
|
217
218
|
approvalPrompt,
|
|
218
|
-
approvalDefaults
|
|
219
|
+
approvalDefaults,
|
|
220
|
+
maxExecutionsPerRun
|
|
219
221
|
}) {
|
|
220
222
|
const execute = async (input) => func(input);
|
|
221
223
|
const toolRecord = {
|
|
@@ -237,25 +239,45 @@ function createTool({
|
|
|
237
239
|
if (approvalDefaults !== void 0) {
|
|
238
240
|
toolRecord.approvalDefaults = approvalDefaults;
|
|
239
241
|
}
|
|
242
|
+
if (maxExecutionsPerRun !== void 0) {
|
|
243
|
+
toolRecord.maxExecutionsPerRun = maxExecutionsPerRun;
|
|
244
|
+
}
|
|
240
245
|
toolRecord.__source = toolRecord.__source || "smart";
|
|
241
246
|
toolRecord.__impl = func;
|
|
242
247
|
return toolRecord;
|
|
243
248
|
}
|
|
244
249
|
|
|
245
250
|
// src/prompts.ts
|
|
246
|
-
|
|
247
|
-
const extraTrimmed = extra?.trim();
|
|
248
|
-
const agentHeader = `Agent Name: ${name}`;
|
|
249
|
-
const planningBlock = planning ? `<planning>
|
|
250
|
-
PLANNING IS MANDATORY.
|
|
251
|
+
var DEFAULT_TODO_LIST_PROMPT = `Planning tools are available when they materially improve execution.
|
|
251
252
|
|
|
252
253
|
Rules:
|
|
253
|
-
1)
|
|
254
|
-
2)
|
|
255
|
-
3)
|
|
256
|
-
4)
|
|
257
|
-
5)
|
|
258
|
-
|
|
254
|
+
1) Use "manage_todo_list" for multi-step work, delegation, recovery after a failed attempt, or when the user explicitly asks for a plan.
|
|
255
|
+
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.
|
|
256
|
+
3) If a task is multi-step and no valid plan exists yet, create one before substantial execution.
|
|
257
|
+
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.
|
|
258
|
+
5) When using operation="update", send only the changed items. Do not resend unchanged plan items unless the whole plan structure is being rewritten.
|
|
259
|
+
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.
|
|
260
|
+
7) Keep exactly ONE item "in-progress" at a time.
|
|
261
|
+
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.
|
|
262
|
+
9) If tool results materially change the task state, sync the plan before the final answer.
|
|
263
|
+
10) Reuse prior tool results already present in the conversation before calling tools again.
|
|
264
|
+
11) If an update fails because of a version mismatch, read the latest plan and retry instead of blindly rewriting it.
|
|
265
|
+
12) Repeating full operation="write" calls for the same plan is a mistake unless the plan structure itself changed.
|
|
266
|
+
13) Keep the plan internal unless the user explicitly asks to see it.`;
|
|
267
|
+
function buildPlanningBlock(todoListPrompt) {
|
|
268
|
+
const promptBody = todoListPrompt?.trim() || DEFAULT_TODO_LIST_PROMPT;
|
|
269
|
+
if (promptBody.startsWith("<planning>")) {
|
|
270
|
+
return promptBody;
|
|
271
|
+
}
|
|
272
|
+
return `<planning>
|
|
273
|
+
${promptBody}
|
|
274
|
+
</planning>`;
|
|
275
|
+
}
|
|
276
|
+
function buildSystemPrompt(extra, planning, name = "Agent", todoListPrompt) {
|
|
277
|
+
const extraTrimmed = extra?.trim();
|
|
278
|
+
const agentHeader = `Agent Name: ${name}`;
|
|
279
|
+
const planningMode = typeof planning === "string" ? planning : planning ? "todo" : "off";
|
|
280
|
+
const planningBlock = planningMode !== "off" ? buildPlanningBlock(todoListPrompt) : "";
|
|
259
281
|
return [
|
|
260
282
|
agentHeader,
|
|
261
283
|
"You are an advanced AI agent that is concise, accurate, and helpful.",
|
|
@@ -367,6 +389,9 @@ function cognipeerSink(first, second) {
|
|
|
367
389
|
function httpSink(url, headers) {
|
|
368
390
|
return { type: "http", url, headers };
|
|
369
391
|
}
|
|
392
|
+
function otlpSink(endpoint, headers) {
|
|
393
|
+
return { type: "otlp", endpoint, headers };
|
|
394
|
+
}
|
|
370
395
|
function resolveLogsBaseDir(customPath, ensureDirectory = true) {
|
|
371
396
|
const root = process.cwd();
|
|
372
397
|
const base = customPath && customPath.trim().length > 0 ? customPath : path.join(root, "logs");
|
|
@@ -566,6 +591,14 @@ function resolveSink(sink) {
|
|
|
566
591
|
const headers = candidate.headers ? { ...candidate.headers } : void 0;
|
|
567
592
|
return headers ? { type: "http", url, headers } : { type: "http", url };
|
|
568
593
|
}
|
|
594
|
+
case "otlp": {
|
|
595
|
+
const endpoint = typeof candidate.endpoint === "string" ? candidate.endpoint.trim() : "";
|
|
596
|
+
if (!endpoint) {
|
|
597
|
+
throw new Error("otlp sink requires a non-empty endpoint");
|
|
598
|
+
}
|
|
599
|
+
const headers = candidate.headers ? { ...candidate.headers } : void 0;
|
|
600
|
+
return headers ? { type: "otlp", endpoint, headers } : { type: "otlp", endpoint };
|
|
601
|
+
}
|
|
569
602
|
default: {
|
|
570
603
|
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
571
604
|
console.warn("Unknown tracing sink type. Falling back to file sink.", candidate);
|
|
@@ -585,6 +618,8 @@ function snapshotSink(runtime) {
|
|
|
585
618
|
return { type: "cognipeer", url: sink.url };
|
|
586
619
|
case "http":
|
|
587
620
|
return { type: "http", url: sink.url };
|
|
621
|
+
case "otlp":
|
|
622
|
+
return { type: "otlp", endpoint: sink.endpoint };
|
|
588
623
|
default:
|
|
589
624
|
return { type: "custom" };
|
|
590
625
|
}
|
|
@@ -735,6 +770,202 @@ async function postStreamingSessionEnd(baseUrl, headers, payload) {
|
|
|
735
770
|
throw err;
|
|
736
771
|
}
|
|
737
772
|
}
|
|
773
|
+
function isoToUnixNano(iso) {
|
|
774
|
+
const ms = new Date(iso).getTime();
|
|
775
|
+
return `${ms}000000`;
|
|
776
|
+
}
|
|
777
|
+
function toOtlpStatusCode(status) {
|
|
778
|
+
if (status === "error") return 2;
|
|
779
|
+
if (status === "success" || status === "completed") return 1;
|
|
780
|
+
return 0;
|
|
781
|
+
}
|
|
782
|
+
function toOtlpSpanKind(type) {
|
|
783
|
+
switch (type) {
|
|
784
|
+
case "ai_call":
|
|
785
|
+
return 3;
|
|
786
|
+
// CLIENT — outgoing LLM request
|
|
787
|
+
case "tool_call":
|
|
788
|
+
return 1;
|
|
789
|
+
// INTERNAL
|
|
790
|
+
case "agent_iteration":
|
|
791
|
+
return 1;
|
|
792
|
+
// INTERNAL
|
|
793
|
+
default:
|
|
794
|
+
return 1;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function stringAttr(key, val) {
|
|
798
|
+
return val != null ? { key, value: { stringValue: val } } : null;
|
|
799
|
+
}
|
|
800
|
+
function intAttr(key, val) {
|
|
801
|
+
return val != null && !Number.isNaN(val) ? { key, value: { intValue: String(val) } } : null;
|
|
802
|
+
}
|
|
803
|
+
function doubleAttr(key, val) {
|
|
804
|
+
return val != null && !Number.isNaN(val) ? { key, value: { doubleValue: val } } : null;
|
|
805
|
+
}
|
|
806
|
+
function eventToOtlpSpan(event) {
|
|
807
|
+
const startNano = isoToUnixNano(event.timestamp);
|
|
808
|
+
const endNano = event.durationMs != null ? `${new Date(new Date(event.timestamp).getTime() + event.durationMs).getTime()}000000` : startNano;
|
|
809
|
+
const attrs = [
|
|
810
|
+
stringAttr("cognipeer.event.type", event.type),
|
|
811
|
+
stringAttr("cognipeer.event.label", event.label),
|
|
812
|
+
intAttr("cognipeer.event.sequence", event.sequence),
|
|
813
|
+
stringAttr("cognipeer.actor.scope", event.actor?.scope),
|
|
814
|
+
stringAttr("cognipeer.actor.name", event.actor?.name),
|
|
815
|
+
stringAttr("cognipeer.actor.role", event.actor?.role),
|
|
816
|
+
stringAttr("cognipeer.model", event.model),
|
|
817
|
+
stringAttr("cognipeer.provider", event.provider),
|
|
818
|
+
intAttr("cognipeer.tokens.input", event.inputTokens),
|
|
819
|
+
intAttr("cognipeer.tokens.output", event.outputTokens),
|
|
820
|
+
intAttr("cognipeer.tokens.total", event.totalTokens),
|
|
821
|
+
intAttr("cognipeer.tokens.cached_input", event.cachedInputTokens),
|
|
822
|
+
intAttr("cognipeer.bytes.request", event.requestBytes),
|
|
823
|
+
intAttr("cognipeer.bytes.response", event.responseBytes),
|
|
824
|
+
stringAttr("cognipeer.tool.execution_id", event.toolExecutionId),
|
|
825
|
+
stringAttr("cognipeer.retry_of", event.retryOf),
|
|
826
|
+
stringAttr("cognipeer.event.id", event.id)
|
|
827
|
+
].filter(Boolean);
|
|
828
|
+
if (event.data?.sections && event.data.sections.length > 0) {
|
|
829
|
+
try {
|
|
830
|
+
attrs.push({ key: "cognipeer.sections", value: { stringValue: JSON.stringify(event.data.sections) } });
|
|
831
|
+
} catch {
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const span = {
|
|
835
|
+
traceId: event.traceId,
|
|
836
|
+
spanId: event.spanId,
|
|
837
|
+
parentSpanId: event.parentSpanId || "",
|
|
838
|
+
name: event.label || event.type,
|
|
839
|
+
kind: toOtlpSpanKind(event.type),
|
|
840
|
+
startTimeUnixNano: startNano,
|
|
841
|
+
endTimeUnixNano: endNano,
|
|
842
|
+
attributes: attrs,
|
|
843
|
+
status: {
|
|
844
|
+
code: toOtlpStatusCode(event.status),
|
|
845
|
+
...event.status === "error" && event.error?.message ? { message: event.error.message } : {}
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
if (event.error) {
|
|
849
|
+
span.events = [{
|
|
850
|
+
timeUnixNano: startNano,
|
|
851
|
+
name: "exception",
|
|
852
|
+
attributes: [
|
|
853
|
+
{ key: "exception.message", value: { stringValue: event.error.message } },
|
|
854
|
+
...event.error.stack ? [{ key: "exception.stacktrace", value: { stringValue: event.error.stack } }] : []
|
|
855
|
+
]
|
|
856
|
+
}];
|
|
857
|
+
}
|
|
858
|
+
return span;
|
|
859
|
+
}
|
|
860
|
+
function traceSessionToOtlp(session) {
|
|
861
|
+
const traceId = session.traceId || generateTraceId();
|
|
862
|
+
const rootSpanId = session.rootSpanId || generateSpanId();
|
|
863
|
+
const startNano = isoToUnixNano(session.startedAt);
|
|
864
|
+
const endNano = session.endedAt ? isoToUnixNano(session.endedAt) : startNano;
|
|
865
|
+
const resourceAttrs = [
|
|
866
|
+
{ key: "service.name", value: { stringValue: session.agent?.name || "cognipeer-agent" } },
|
|
867
|
+
stringAttr("service.version", session.agent?.version),
|
|
868
|
+
stringAttr("cognipeer.session.id", session.sessionId),
|
|
869
|
+
stringAttr("cognipeer.thread.id", session.threadId),
|
|
870
|
+
stringAttr("cognipeer.agent.model", session.agent?.model),
|
|
871
|
+
stringAttr("cognipeer.agent.provider", session.agent?.provider)
|
|
872
|
+
].filter(Boolean);
|
|
873
|
+
const rootAttrs = [
|
|
874
|
+
stringAttr("cognipeer.session.status", session.status),
|
|
875
|
+
doubleAttr("cognipeer.session.duration_ms", session.durationMs),
|
|
876
|
+
intAttr("cognipeer.session.total_input_tokens", session.summary.totalInputTokens),
|
|
877
|
+
intAttr("cognipeer.session.total_output_tokens", session.summary.totalOutputTokens),
|
|
878
|
+
intAttr("cognipeer.session.total_cached_input_tokens", session.summary.totalCachedInputTokens),
|
|
879
|
+
intAttr("cognipeer.session.total_bytes_in", session.summary.totalBytesIn),
|
|
880
|
+
intAttr("cognipeer.session.total_bytes_out", session.summary.totalBytesOut),
|
|
881
|
+
intAttr("cognipeer.session.event_count", session.events.length)
|
|
882
|
+
].filter(Boolean);
|
|
883
|
+
if (session.summary.eventCounts) {
|
|
884
|
+
for (const [key, count] of Object.entries(session.summary.eventCounts)) {
|
|
885
|
+
rootAttrs.push({ key: `cognipeer.session.event_count.${key}`, value: { intValue: String(count) } });
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
const rootSpan = {
|
|
889
|
+
traceId,
|
|
890
|
+
spanId: rootSpanId,
|
|
891
|
+
parentSpanId: "",
|
|
892
|
+
name: `agent_session: ${session.agent?.name || "agent"}`,
|
|
893
|
+
kind: 1,
|
|
894
|
+
// INTERNAL
|
|
895
|
+
startTimeUnixNano: startNano,
|
|
896
|
+
endTimeUnixNano: endNano,
|
|
897
|
+
attributes: rootAttrs,
|
|
898
|
+
status: {
|
|
899
|
+
code: toOtlpStatusCode(session.status),
|
|
900
|
+
...session.status === "error" && session.errors.length > 0 ? { message: session.errors.map((e) => e.message).join("; ") } : {}
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
if (session.errors.length > 0) {
|
|
904
|
+
rootSpan.events = session.errors.map((err) => ({
|
|
905
|
+
timeUnixNano: err.timestamp ? isoToUnixNano(err.timestamp) : startNano,
|
|
906
|
+
name: "exception",
|
|
907
|
+
attributes: [
|
|
908
|
+
{ key: "exception.message", value: { stringValue: err.message } },
|
|
909
|
+
...err.stack ? [{ key: "exception.stacktrace", value: { stringValue: err.stack } }] : [],
|
|
910
|
+
...err.type ? [{ key: "exception.type", value: { stringValue: err.type } }] : []
|
|
911
|
+
]
|
|
912
|
+
}));
|
|
913
|
+
}
|
|
914
|
+
const childSpans = session.events.map((event) => {
|
|
915
|
+
const span = eventToOtlpSpan({
|
|
916
|
+
...event,
|
|
917
|
+
traceId: event.traceId || traceId,
|
|
918
|
+
spanId: event.spanId || generateSpanId(),
|
|
919
|
+
parentSpanId: event.parentSpanId || rootSpanId
|
|
920
|
+
});
|
|
921
|
+
return span;
|
|
922
|
+
});
|
|
923
|
+
return {
|
|
924
|
+
resourceSpans: [{
|
|
925
|
+
resource: { attributes: resourceAttrs },
|
|
926
|
+
scopeSpans: [{
|
|
927
|
+
scope: {
|
|
928
|
+
name: "cognipeer-agent-sdk",
|
|
929
|
+
version: "1.0.0"
|
|
930
|
+
},
|
|
931
|
+
spans: [rootSpan, ...childSpans]
|
|
932
|
+
}]
|
|
933
|
+
}]
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
async function postOtlpTraces(endpoint, headers, payload) {
|
|
937
|
+
const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
|
|
938
|
+
if (!fetchFn) {
|
|
939
|
+
throw new Error("OTLP sink requires fetch to be available in this runtime.");
|
|
940
|
+
}
|
|
941
|
+
const finalHeaders = {
|
|
942
|
+
"content-type": "application/json",
|
|
943
|
+
...headers || {}
|
|
944
|
+
};
|
|
945
|
+
try {
|
|
946
|
+
const response = await fetchFn(endpoint, {
|
|
947
|
+
method: "POST",
|
|
948
|
+
headers: finalHeaders,
|
|
949
|
+
body: JSON.stringify(payload)
|
|
950
|
+
});
|
|
951
|
+
if (!response.ok) {
|
|
952
|
+
let responseText = "";
|
|
953
|
+
try {
|
|
954
|
+
responseText = await response.text();
|
|
955
|
+
} catch {
|
|
956
|
+
}
|
|
957
|
+
const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
|
|
958
|
+
const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
|
|
959
|
+
throw new Error(`${statusLine}${bodyPreview}`);
|
|
960
|
+
}
|
|
961
|
+
} catch (err) {
|
|
962
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
963
|
+
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
964
|
+
console.error("[Tracing] OTLP export failed:", errMsg);
|
|
965
|
+
}
|
|
966
|
+
throw err;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
738
969
|
function createEmptySummary() {
|
|
739
970
|
return {
|
|
740
971
|
totalDurationMs: 0,
|
|
@@ -752,6 +983,12 @@ function generateSessionId() {
|
|
|
752
983
|
function generateEventId(sequence) {
|
|
753
984
|
return `evt_${String(sequence).padStart(4, "0")}_${nanoid(4)}`;
|
|
754
985
|
}
|
|
986
|
+
function generateTraceId() {
|
|
987
|
+
return crypto.randomBytes(16).toString("hex");
|
|
988
|
+
}
|
|
989
|
+
function generateSpanId() {
|
|
990
|
+
return crypto.randomBytes(8).toString("hex");
|
|
991
|
+
}
|
|
755
992
|
function defaultEventLabel(type) {
|
|
756
993
|
switch (type) {
|
|
757
994
|
case "ai_call":
|
|
@@ -904,8 +1141,12 @@ function createTraceSession(opts) {
|
|
|
904
1141
|
const cfg = withDefaults(opts.tracing);
|
|
905
1142
|
if (!cfg.enabled) return void 0;
|
|
906
1143
|
const sessionId = generateSessionId();
|
|
1144
|
+
const traceId = generateTraceId();
|
|
1145
|
+
const rootSpanId = generateSpanId();
|
|
907
1146
|
const runtime = {
|
|
908
1147
|
sessionId,
|
|
1148
|
+
traceId,
|
|
1149
|
+
rootSpanId,
|
|
909
1150
|
threadId: opts.tracing?.threadId,
|
|
910
1151
|
startedAt: Date.now(),
|
|
911
1152
|
resolvedConfig: cfg,
|
|
@@ -1015,8 +1256,13 @@ function recordTraceEvent(session, event) {
|
|
|
1015
1256
|
const resolvedModel = event.model ?? inferModelFromMessages(event.messageList);
|
|
1016
1257
|
const resolvedProvider = event.provider ?? inferProviderFromMessages(event.messageList);
|
|
1017
1258
|
const isAiCall = event.type === "ai_call";
|
|
1259
|
+
const spanId = generateSpanId();
|
|
1260
|
+
const parentSpanId = event.parentSpanId || session.currentIterationSpanId || session.rootSpanId;
|
|
1018
1261
|
const record = {
|
|
1019
1262
|
sessionId: session.sessionId,
|
|
1263
|
+
traceId: session.traceId,
|
|
1264
|
+
spanId,
|
|
1265
|
+
parentSpanId,
|
|
1020
1266
|
id,
|
|
1021
1267
|
type: event.type,
|
|
1022
1268
|
label: eventLabel,
|
|
@@ -1123,6 +1369,8 @@ async function finalizeTraceSession(session, params = {}) {
|
|
|
1123
1369
|
const initialStatus = params.status ? params.status : session.errors.length > 0 ? "error" : "success";
|
|
1124
1370
|
const buildPayload = (status2) => ({
|
|
1125
1371
|
sessionId: session.sessionId,
|
|
1372
|
+
traceId: session.traceId,
|
|
1373
|
+
rootSpanId: session.rootSpanId,
|
|
1126
1374
|
threadId: session.threadId,
|
|
1127
1375
|
startedAt: startedAtIso,
|
|
1128
1376
|
endedAt: endedAtIso,
|
|
@@ -1138,7 +1386,21 @@ async function finalizeTraceSession(session, params = {}) {
|
|
|
1138
1386
|
let sinkFailed = false;
|
|
1139
1387
|
const sink = session.resolvedConfig.sink;
|
|
1140
1388
|
const isStreamingMode = session.resolvedConfig.mode === "streaming";
|
|
1141
|
-
if (sink.type === "
|
|
1389
|
+
if (sink.type === "otlp") {
|
|
1390
|
+
try {
|
|
1391
|
+
const otlpPayload = traceSessionToOtlp(payloadForSink);
|
|
1392
|
+
await postOtlpTraces(sink.endpoint, sink.headers, otlpPayload);
|
|
1393
|
+
} catch (err) {
|
|
1394
|
+
sinkFailed = true;
|
|
1395
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1396
|
+
session.errors.push({
|
|
1397
|
+
eventId: "sink",
|
|
1398
|
+
message,
|
|
1399
|
+
type: "sink",
|
|
1400
|
+
timestamp: endedAtIso
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
} else if (sink.type === "cognipeer" || sink.type === "http") {
|
|
1142
1404
|
try {
|
|
1143
1405
|
const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
|
|
1144
1406
|
if (isStreamingMode && session.sessionStarted) {
|
|
@@ -1165,8 +1427,9 @@ async function finalizeTraceSession(session, params = {}) {
|
|
|
1165
1427
|
}
|
|
1166
1428
|
}
|
|
1167
1429
|
const hadNonSinkErrors = session.errors.some((error) => error.type && error.type !== "sink");
|
|
1430
|
+
const hadSinkErrors = session.errors.some((error) => error.type === "sink");
|
|
1168
1431
|
let status = params.status ?? (hadNonSinkErrors ? "error" : "success");
|
|
1169
|
-
if (status === "success" && sinkFailed) {
|
|
1432
|
+
if (status === "success" && (sinkFailed || hadSinkErrors)) {
|
|
1170
1433
|
status = "partial";
|
|
1171
1434
|
}
|
|
1172
1435
|
let finalPayload = buildPayload(status);
|
|
@@ -1270,6 +1533,7 @@ function createAgentCoreNode(opts) {
|
|
|
1270
1533
|
model: opts.model,
|
|
1271
1534
|
tools: opts.tools || [],
|
|
1272
1535
|
guardrails: opts.guardrails,
|
|
1536
|
+
todoListPrompt: opts.todoListPrompt,
|
|
1273
1537
|
limits: opts.limits,
|
|
1274
1538
|
outputSchema: opts.outputSchema,
|
|
1275
1539
|
tracing: opts.tracing
|
|
@@ -1344,7 +1608,7 @@ function createAgentCoreNode(opts) {
|
|
|
1344
1608
|
}
|
|
1345
1609
|
}
|
|
1346
1610
|
}
|
|
1347
|
-
} catch
|
|
1611
|
+
} catch {
|
|
1348
1612
|
}
|
|
1349
1613
|
};
|
|
1350
1614
|
const onEvent = state.ctx?.__onEvent;
|
|
@@ -1460,9 +1724,523 @@ function createAgentCoreNode(opts) {
|
|
|
1460
1724
|
return { messages: messagesWithResponse, usage: state.usage };
|
|
1461
1725
|
};
|
|
1462
1726
|
}
|
|
1727
|
+
|
|
1728
|
+
// src/smart/runtimeConfig.ts
|
|
1729
|
+
var DEFAULT_CRITICAL_TOOLS = ["response", "manage_todo_list", "get_tool_response"];
|
|
1730
|
+
var DEFAULT_PROFILE_CONFIGS = {
|
|
1731
|
+
fast: {
|
|
1732
|
+
limits: { maxToolCalls: 4, maxParallelTools: 1, maxContextTokens: 12e3 },
|
|
1733
|
+
summarization: {
|
|
1734
|
+
enable: true,
|
|
1735
|
+
maxTokens: 12e3,
|
|
1736
|
+
summaryTriggerTokens: 9e3,
|
|
1737
|
+
summaryPromptMaxTokens: 5e3,
|
|
1738
|
+
summaryCompressionRatioTarget: 0.4,
|
|
1739
|
+
summaryMode: "incremental",
|
|
1740
|
+
promptTemplate: "",
|
|
1741
|
+
toolFreeCall: true,
|
|
1742
|
+
integrityCheck: true
|
|
1743
|
+
},
|
|
1744
|
+
context: {
|
|
1745
|
+
policy: "hybrid",
|
|
1746
|
+
lastTurnsToKeep: 6,
|
|
1747
|
+
toolResponsePolicy: "keep_structured",
|
|
1748
|
+
archiveLargeToolResponses: true,
|
|
1749
|
+
retrieveArchivedToolResponseOnDemand: true,
|
|
1750
|
+
budget: {
|
|
1751
|
+
systemReserveTokens: 1e3,
|
|
1752
|
+
goalsReserveTokens: 900,
|
|
1753
|
+
recentTurnsReserveTokens: 3200,
|
|
1754
|
+
toolResponseReserveTokens: 1800
|
|
1755
|
+
}
|
|
1756
|
+
},
|
|
1757
|
+
planning: {
|
|
1758
|
+
mode: "off",
|
|
1759
|
+
replanPolicy: "on_failure",
|
|
1760
|
+
everyNSteps: 0
|
|
1761
|
+
},
|
|
1762
|
+
memory: {
|
|
1763
|
+
provider: "inMemory",
|
|
1764
|
+
scope: "session",
|
|
1765
|
+
writePolicy: "auto_important",
|
|
1766
|
+
readPolicy: "recent_only"
|
|
1767
|
+
},
|
|
1768
|
+
delegation: {
|
|
1769
|
+
mode: "off",
|
|
1770
|
+
maxDelegationDepth: 1,
|
|
1771
|
+
maxChildCalls: 2,
|
|
1772
|
+
maxParallelChild: 1,
|
|
1773
|
+
childContextPolicy: "minimal",
|
|
1774
|
+
requireJsonOutputContract: true
|
|
1775
|
+
},
|
|
1776
|
+
toolResponses: {
|
|
1777
|
+
maxToolResponseChars: 2400,
|
|
1778
|
+
maxToolResponseTokens: 800,
|
|
1779
|
+
defaultPolicy: "keep_structured",
|
|
1780
|
+
largeResponsePolicy: "summarize_archive",
|
|
1781
|
+
toolResponseRetentionByTool: {},
|
|
1782
|
+
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1783
|
+
schemaValidation: "strict",
|
|
1784
|
+
retryOnSchemaError: true,
|
|
1785
|
+
fallbackPolicy: "keep_structured"
|
|
1786
|
+
},
|
|
1787
|
+
watchdog: {
|
|
1788
|
+
enabled: true,
|
|
1789
|
+
tokenDriftThreshold: 1200,
|
|
1790
|
+
overToolingSpikeThreshold: 0.7,
|
|
1791
|
+
contextRotThreshold: 0.65,
|
|
1792
|
+
autoCompaction: true,
|
|
1793
|
+
autoReplanOnFailure: true
|
|
1794
|
+
}
|
|
1795
|
+
},
|
|
1796
|
+
balanced: {
|
|
1797
|
+
limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3 },
|
|
1798
|
+
summarization: {
|
|
1799
|
+
enable: true,
|
|
1800
|
+
maxTokens: 24e3,
|
|
1801
|
+
summaryTriggerTokens: 17e3,
|
|
1802
|
+
summaryPromptMaxTokens: 7e3,
|
|
1803
|
+
summaryCompressionRatioTarget: 0.35,
|
|
1804
|
+
summaryMode: "incremental",
|
|
1805
|
+
promptTemplate: "",
|
|
1806
|
+
toolFreeCall: true,
|
|
1807
|
+
integrityCheck: true
|
|
1808
|
+
},
|
|
1809
|
+
context: {
|
|
1810
|
+
policy: "hybrid",
|
|
1811
|
+
lastTurnsToKeep: 8,
|
|
1812
|
+
toolResponsePolicy: "summarize_archive",
|
|
1813
|
+
archiveLargeToolResponses: true,
|
|
1814
|
+
retrieveArchivedToolResponseOnDemand: true,
|
|
1815
|
+
budget: {
|
|
1816
|
+
systemReserveTokens: 1200,
|
|
1817
|
+
goalsReserveTokens: 1200,
|
|
1818
|
+
recentTurnsReserveTokens: 5200,
|
|
1819
|
+
toolResponseReserveTokens: 2800
|
|
1820
|
+
}
|
|
1821
|
+
},
|
|
1822
|
+
planning: {
|
|
1823
|
+
mode: "off",
|
|
1824
|
+
replanPolicy: "on_failure",
|
|
1825
|
+
everyNSteps: 0
|
|
1826
|
+
},
|
|
1827
|
+
memory: {
|
|
1828
|
+
provider: "inMemory",
|
|
1829
|
+
scope: "session",
|
|
1830
|
+
writePolicy: "auto_important",
|
|
1831
|
+
readPolicy: "hybrid"
|
|
1832
|
+
},
|
|
1833
|
+
delegation: {
|
|
1834
|
+
mode: "role_based",
|
|
1835
|
+
maxDelegationDepth: 2,
|
|
1836
|
+
maxChildCalls: 4,
|
|
1837
|
+
maxParallelChild: 2,
|
|
1838
|
+
childContextPolicy: "scoped",
|
|
1839
|
+
requireJsonOutputContract: true
|
|
1840
|
+
},
|
|
1841
|
+
toolResponses: {
|
|
1842
|
+
maxToolResponseChars: 3600,
|
|
1843
|
+
maxToolResponseTokens: 1200,
|
|
1844
|
+
defaultPolicy: "keep_structured",
|
|
1845
|
+
largeResponsePolicy: "summarize_archive",
|
|
1846
|
+
toolResponseRetentionByTool: {},
|
|
1847
|
+
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1848
|
+
schemaValidation: "strict",
|
|
1849
|
+
retryOnSchemaError: true,
|
|
1850
|
+
fallbackPolicy: "keep_structured"
|
|
1851
|
+
},
|
|
1852
|
+
watchdog: {
|
|
1853
|
+
enabled: true,
|
|
1854
|
+
tokenDriftThreshold: 1800,
|
|
1855
|
+
overToolingSpikeThreshold: 0.6,
|
|
1856
|
+
contextRotThreshold: 0.55,
|
|
1857
|
+
autoCompaction: true,
|
|
1858
|
+
autoReplanOnFailure: true
|
|
1859
|
+
}
|
|
1860
|
+
},
|
|
1861
|
+
deep: {
|
|
1862
|
+
limits: { maxToolCalls: 14, maxParallelTools: 3, maxContextTokens: 42e3 },
|
|
1863
|
+
summarization: {
|
|
1864
|
+
enable: true,
|
|
1865
|
+
maxTokens: 42e3,
|
|
1866
|
+
summaryTriggerTokens: 3e4,
|
|
1867
|
+
summaryPromptMaxTokens: 9e3,
|
|
1868
|
+
summaryCompressionRatioTarget: 0.3,
|
|
1869
|
+
summaryMode: "incremental",
|
|
1870
|
+
promptTemplate: "",
|
|
1871
|
+
toolFreeCall: true,
|
|
1872
|
+
integrityCheck: true
|
|
1873
|
+
},
|
|
1874
|
+
context: {
|
|
1875
|
+
policy: "hybrid",
|
|
1876
|
+
lastTurnsToKeep: 12,
|
|
1877
|
+
toolResponsePolicy: "summarize_archive",
|
|
1878
|
+
archiveLargeToolResponses: true,
|
|
1879
|
+
retrieveArchivedToolResponseOnDemand: true,
|
|
1880
|
+
budget: {
|
|
1881
|
+
systemReserveTokens: 1400,
|
|
1882
|
+
goalsReserveTokens: 1600,
|
|
1883
|
+
recentTurnsReserveTokens: 7e3,
|
|
1884
|
+
toolResponseReserveTokens: 3600
|
|
1885
|
+
}
|
|
1886
|
+
},
|
|
1887
|
+
planning: {
|
|
1888
|
+
mode: "off",
|
|
1889
|
+
replanPolicy: "on_failure",
|
|
1890
|
+
everyNSteps: 0
|
|
1891
|
+
},
|
|
1892
|
+
memory: {
|
|
1893
|
+
provider: "inMemory",
|
|
1894
|
+
scope: "workspace",
|
|
1895
|
+
writePolicy: "auto_important",
|
|
1896
|
+
readPolicy: "hybrid"
|
|
1897
|
+
},
|
|
1898
|
+
delegation: {
|
|
1899
|
+
mode: "role_based",
|
|
1900
|
+
maxDelegationDepth: 3,
|
|
1901
|
+
maxChildCalls: 6,
|
|
1902
|
+
maxParallelChild: 2,
|
|
1903
|
+
childContextPolicy: "scoped",
|
|
1904
|
+
requireJsonOutputContract: true
|
|
1905
|
+
},
|
|
1906
|
+
toolResponses: {
|
|
1907
|
+
maxToolResponseChars: 5400,
|
|
1908
|
+
maxToolResponseTokens: 1800,
|
|
1909
|
+
defaultPolicy: "keep_structured",
|
|
1910
|
+
largeResponsePolicy: "summarize_archive",
|
|
1911
|
+
toolResponseRetentionByTool: {},
|
|
1912
|
+
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1913
|
+
schemaValidation: "strict",
|
|
1914
|
+
retryOnSchemaError: true,
|
|
1915
|
+
fallbackPolicy: "keep_structured"
|
|
1916
|
+
},
|
|
1917
|
+
watchdog: {
|
|
1918
|
+
enabled: true,
|
|
1919
|
+
tokenDriftThreshold: 2200,
|
|
1920
|
+
overToolingSpikeThreshold: 0.55,
|
|
1921
|
+
contextRotThreshold: 0.5,
|
|
1922
|
+
autoCompaction: true,
|
|
1923
|
+
autoReplanOnFailure: true
|
|
1924
|
+
}
|
|
1925
|
+
},
|
|
1926
|
+
research: {
|
|
1927
|
+
limits: { maxToolCalls: 20, maxParallelTools: 4, maxContextTokens: 56e3 },
|
|
1928
|
+
summarization: {
|
|
1929
|
+
enable: true,
|
|
1930
|
+
maxTokens: 56e3,
|
|
1931
|
+
summaryTriggerTokens: 42e3,
|
|
1932
|
+
summaryPromptMaxTokens: 1e4,
|
|
1933
|
+
summaryCompressionRatioTarget: 0.28,
|
|
1934
|
+
summaryMode: "incremental",
|
|
1935
|
+
promptTemplate: "",
|
|
1936
|
+
toolFreeCall: true,
|
|
1937
|
+
integrityCheck: true
|
|
1938
|
+
},
|
|
1939
|
+
context: {
|
|
1940
|
+
policy: "hybrid",
|
|
1941
|
+
lastTurnsToKeep: 20,
|
|
1942
|
+
toolResponsePolicy: "summarize_archive",
|
|
1943
|
+
archiveLargeToolResponses: true,
|
|
1944
|
+
retrieveArchivedToolResponseOnDemand: true,
|
|
1945
|
+
budget: {
|
|
1946
|
+
systemReserveTokens: 1600,
|
|
1947
|
+
goalsReserveTokens: 1800,
|
|
1948
|
+
recentTurnsReserveTokens: 9e3,
|
|
1949
|
+
toolResponseReserveTokens: 4800
|
|
1950
|
+
}
|
|
1951
|
+
},
|
|
1952
|
+
planning: {
|
|
1953
|
+
mode: "off",
|
|
1954
|
+
replanPolicy: "on_failure",
|
|
1955
|
+
everyNSteps: 0
|
|
1956
|
+
},
|
|
1957
|
+
memory: {
|
|
1958
|
+
provider: "inMemory",
|
|
1959
|
+
scope: "workspace",
|
|
1960
|
+
writePolicy: "auto_important",
|
|
1961
|
+
readPolicy: "hybrid"
|
|
1962
|
+
},
|
|
1963
|
+
delegation: {
|
|
1964
|
+
mode: "automatic",
|
|
1965
|
+
maxDelegationDepth: 4,
|
|
1966
|
+
maxChildCalls: 8,
|
|
1967
|
+
maxParallelChild: 3,
|
|
1968
|
+
childContextPolicy: "full",
|
|
1969
|
+
requireJsonOutputContract: true
|
|
1970
|
+
},
|
|
1971
|
+
toolResponses: {
|
|
1972
|
+
maxToolResponseChars: 7200,
|
|
1973
|
+
maxToolResponseTokens: 2200,
|
|
1974
|
+
defaultPolicy: "keep_structured",
|
|
1975
|
+
largeResponsePolicy: "summarize_archive",
|
|
1976
|
+
toolResponseRetentionByTool: {},
|
|
1977
|
+
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1978
|
+
schemaValidation: "strict",
|
|
1979
|
+
retryOnSchemaError: true,
|
|
1980
|
+
fallbackPolicy: "keep_structured"
|
|
1981
|
+
},
|
|
1982
|
+
watchdog: {
|
|
1983
|
+
enabled: true,
|
|
1984
|
+
tokenDriftThreshold: 2600,
|
|
1985
|
+
overToolingSpikeThreshold: 0.5,
|
|
1986
|
+
contextRotThreshold: 0.45,
|
|
1987
|
+
autoCompaction: true,
|
|
1988
|
+
autoReplanOnFailure: true
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
function mergeLimits(base, override) {
|
|
1993
|
+
return {
|
|
1994
|
+
maxToolCalls: override?.maxToolCalls ?? base.maxToolCalls,
|
|
1995
|
+
maxParallelTools: override?.maxParallelTools ?? base.maxParallelTools,
|
|
1996
|
+
maxContextTokens: override?.maxContextTokens ?? base.maxContextTokens
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
function resolveBaseProfile(opts, runtimeProfile) {
|
|
2000
|
+
if (opts.customProfile?.extends) return opts.customProfile.extends;
|
|
2001
|
+
if (runtimeProfile !== "custom") return runtimeProfile;
|
|
2002
|
+
return "balanced";
|
|
2003
|
+
}
|
|
2004
|
+
function normalizeSmartAgentOptions(opts) {
|
|
2005
|
+
const runtimeProfile = opts.runtimeProfile ?? (opts.customProfile ? "custom" : "balanced");
|
|
2006
|
+
const baseProfile = resolveBaseProfile(opts, runtimeProfile);
|
|
2007
|
+
const preset = DEFAULT_PROFILE_CONFIGS[baseProfile];
|
|
2008
|
+
const customProfile = opts.customProfile || {};
|
|
2009
|
+
const useTodoList = opts.useTodoList === true;
|
|
2010
|
+
const summarizationDisabled = opts.summarization === false;
|
|
2011
|
+
const summarizationOverride = typeof opts.summarization === "object" ? opts.summarization : {};
|
|
2012
|
+
const planningMode = opts.planning?.mode ?? customProfile.planning?.mode ?? (useTodoList ? "todo" : preset.planning.mode);
|
|
2013
|
+
const memoryStore = opts.memory?.store ?? customProfile.memory?.store;
|
|
2014
|
+
return {
|
|
2015
|
+
runtimeProfile,
|
|
2016
|
+
baseProfile,
|
|
2017
|
+
limits: mergeLimits(mergeLimits(preset.limits, customProfile.limits), opts.limits),
|
|
2018
|
+
summarization: {
|
|
2019
|
+
...preset.summarization,
|
|
2020
|
+
...customProfile.summarization || {},
|
|
2021
|
+
...summarizationOverride,
|
|
2022
|
+
enable: summarizationDisabled ? false : summarizationOverride.enable ?? customProfile.summarization?.enable ?? preset.summarization.enable,
|
|
2023
|
+
maxTokens: summarizationOverride.maxTokens ?? opts.limits?.maxContextTokens ?? customProfile.summarization?.maxTokens ?? preset.summarization.maxTokens,
|
|
2024
|
+
summaryTriggerTokens: summarizationOverride.summaryTriggerTokens ?? summarizationOverride.maxTokens ?? opts.limits?.maxContextTokens ?? customProfile.summarization?.summaryTriggerTokens ?? customProfile.summarization?.maxTokens ?? preset.summarization.summaryTriggerTokens,
|
|
2025
|
+
promptTemplate: summarizationOverride.promptTemplate ?? customProfile.summarization?.promptTemplate ?? preset.summarization.promptTemplate
|
|
2026
|
+
},
|
|
2027
|
+
context: {
|
|
2028
|
+
...preset.context,
|
|
2029
|
+
...customProfile.context || {},
|
|
2030
|
+
...opts.context || {},
|
|
2031
|
+
budget: {
|
|
2032
|
+
...preset.context.budget,
|
|
2033
|
+
...customProfile.context?.budget || {},
|
|
2034
|
+
...opts.context?.budget || {}
|
|
2035
|
+
},
|
|
2036
|
+
toolResponsePolicy: opts.context?.toolResponsePolicy ?? customProfile.context?.toolResponsePolicy ?? preset.context.toolResponsePolicy
|
|
2037
|
+
},
|
|
2038
|
+
planning: {
|
|
2039
|
+
...preset.planning,
|
|
2040
|
+
...customProfile.planning || {},
|
|
2041
|
+
...opts.planning || {},
|
|
2042
|
+
mode: planningMode
|
|
2043
|
+
},
|
|
2044
|
+
memory: {
|
|
2045
|
+
...preset.memory,
|
|
2046
|
+
...customProfile.memory || {},
|
|
2047
|
+
...opts.memory || {},
|
|
2048
|
+
store: memoryStore
|
|
2049
|
+
},
|
|
2050
|
+
delegation: {
|
|
2051
|
+
...preset.delegation,
|
|
2052
|
+
...customProfile.delegation || {},
|
|
2053
|
+
...opts.delegation || {}
|
|
2054
|
+
},
|
|
2055
|
+
toolResponses: {
|
|
2056
|
+
...preset.toolResponses,
|
|
2057
|
+
...customProfile.toolResponses || {},
|
|
2058
|
+
...opts.toolResponses || {},
|
|
2059
|
+
toolResponseRetentionByTool: {
|
|
2060
|
+
...preset.toolResponses.toolResponseRetentionByTool,
|
|
2061
|
+
...customProfile.toolResponses?.toolResponseRetentionByTool || {},
|
|
2062
|
+
...opts.toolResponses?.toolResponseRetentionByTool || {}
|
|
2063
|
+
},
|
|
2064
|
+
criticalTools: opts.toolResponses?.criticalTools ?? customProfile.toolResponses?.criticalTools ?? preset.toolResponses.criticalTools
|
|
2065
|
+
},
|
|
2066
|
+
watchdog: {
|
|
2067
|
+
...preset.watchdog,
|
|
2068
|
+
...customProfile.watchdog || {},
|
|
2069
|
+
...opts.watchdog || {}
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
function getResolvedSmartConfig(opts, runtime) {
|
|
2074
|
+
return runtime?.smart || normalizeSmartAgentOptions(opts);
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
// src/utils/utilTokens.ts
|
|
2078
|
+
function countApproxTokens(text) {
|
|
2079
|
+
if (!text) return 0;
|
|
2080
|
+
const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
|
|
2081
|
+
const asciiCount = text.length - nonAsciiCount;
|
|
2082
|
+
return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
|
|
2083
|
+
}
|
|
2084
|
+
function extractMessageText(m) {
|
|
2085
|
+
if (!m) return "";
|
|
2086
|
+
const parts = [];
|
|
2087
|
+
parts.push(m.role || "");
|
|
2088
|
+
if (m.name) parts.push(m.name);
|
|
2089
|
+
if (typeof m.content === "string") {
|
|
2090
|
+
parts.push(m.content);
|
|
2091
|
+
} else if (Array.isArray(m.content)) {
|
|
2092
|
+
for (const c of m.content) {
|
|
2093
|
+
if (typeof c === "string") parts.push(c);
|
|
2094
|
+
else if (c?.text) parts.push(c.text);
|
|
2095
|
+
else if (c?.content) parts.push(c.content);
|
|
2096
|
+
else if (c?.type === "tool_use" && c?.input) {
|
|
2097
|
+
parts.push(c.name || "");
|
|
2098
|
+
parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
|
|
2099
|
+
} else if (c != null && typeof c === "object") {
|
|
2100
|
+
parts.push(JSON.stringify(c));
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
} else if (m.content != null && typeof m.content === "object") {
|
|
2104
|
+
parts.push(JSON.stringify(m.content));
|
|
2105
|
+
}
|
|
2106
|
+
if (Array.isArray(m.tool_calls)) {
|
|
2107
|
+
for (const tc of m.tool_calls) {
|
|
2108
|
+
if (tc?.function?.name) parts.push(tc.function.name);
|
|
2109
|
+
if (tc?.function?.arguments) {
|
|
2110
|
+
parts.push(
|
|
2111
|
+
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
if (tc?.name && !tc?.function?.name) parts.push(tc.name);
|
|
2115
|
+
if (tc?.args && !tc?.function?.arguments) {
|
|
2116
|
+
parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
if (Array.isArray(m.additional_kwargs?.tool_calls)) {
|
|
2121
|
+
for (const tc of m.additional_kwargs.tool_calls) {
|
|
2122
|
+
if (tc?.function?.name) parts.push(tc.function.name);
|
|
2123
|
+
if (tc?.function?.arguments) {
|
|
2124
|
+
parts.push(
|
|
2125
|
+
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
return parts.join("\n");
|
|
2131
|
+
}
|
|
2132
|
+
function countMessagesTokens(messages) {
|
|
2133
|
+
if (!Array.isArray(messages) || messages.length === 0) return 0;
|
|
2134
|
+
const allText = messages.map(extractMessageText).join("\n");
|
|
2135
|
+
return countApproxTokens(allText) + messages.length * 4;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// src/smart/toolResponses.ts
|
|
2139
|
+
function safeStringify(value) {
|
|
2140
|
+
if (typeof value === "string") return value;
|
|
2141
|
+
try {
|
|
2142
|
+
return JSON.stringify(value);
|
|
2143
|
+
} catch {
|
|
2144
|
+
return String(value);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
function summarizeObject(value) {
|
|
2148
|
+
if (value == null) return "null";
|
|
2149
|
+
if (typeof value === "string") {
|
|
2150
|
+
return value.length > 240 ? `${value.slice(0, 240)}...` : value;
|
|
2151
|
+
}
|
|
2152
|
+
if (Array.isArray(value)) {
|
|
2153
|
+
const preview = value.slice(0, 3).map((entry) => safeStringify(entry).slice(0, 120)).join(" | ");
|
|
2154
|
+
return `array(length=${value.length}) ${preview}`.trim();
|
|
2155
|
+
}
|
|
2156
|
+
if (typeof value === "object") {
|
|
2157
|
+
const keys = Object.keys(value);
|
|
2158
|
+
const preview = keys.slice(0, 6).map((key) => `${key}=${safeStringify(value[key]).slice(0, 80)}`).join("; ");
|
|
2159
|
+
return `object(keys=${keys.length}) ${preview}`.trim();
|
|
2160
|
+
}
|
|
2161
|
+
return safeStringify(value);
|
|
2162
|
+
}
|
|
2163
|
+
function classifyToolResponse(toolName, output, config) {
|
|
2164
|
+
const serialized = safeStringify(output);
|
|
2165
|
+
const tokenCount = countApproxTokens(serialized);
|
|
2166
|
+
const criticalTools = new Set(config.toolResponses.criticalTools);
|
|
2167
|
+
if (criticalTools.has(toolName)) return "critical";
|
|
2168
|
+
if (tokenCount >= config.toolResponses.maxToolResponseTokens || serialized.length >= config.toolResponses.maxToolResponseChars) {
|
|
2169
|
+
return "verbose";
|
|
2170
|
+
}
|
|
2171
|
+
if (serialized === "ok" || serialized === "true" || serialized === "null") {
|
|
2172
|
+
return "redundant";
|
|
2173
|
+
}
|
|
2174
|
+
return "informative";
|
|
2175
|
+
}
|
|
2176
|
+
function resolveToolResponsePolicy(toolName, output, config) {
|
|
2177
|
+
const serialized = safeStringify(output);
|
|
2178
|
+
const tokenCount = countApproxTokens(serialized);
|
|
2179
|
+
const classification = classifyToolResponse(toolName, output, config);
|
|
2180
|
+
const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
|
|
2181
|
+
let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
|
|
2182
|
+
if (classification === "critical") {
|
|
2183
|
+
retentionPolicy = byTool || "keep_structured";
|
|
2184
|
+
} else if (classification === "verbose") {
|
|
2185
|
+
retentionPolicy = byTool || config.toolResponses.largeResponsePolicy;
|
|
2186
|
+
} else if (classification === "redundant") {
|
|
2187
|
+
retentionPolicy = byTool || "drop";
|
|
2188
|
+
}
|
|
2189
|
+
const summary = summarizeObject(output);
|
|
2190
|
+
if (retentionPolicy === "keep_full") {
|
|
2191
|
+
return { classification, retentionPolicy, content: serialized, rawOutput: output, summary, tokenCount };
|
|
2192
|
+
}
|
|
2193
|
+
if (retentionPolicy === "keep_structured") {
|
|
2194
|
+
return { classification, retentionPolicy, content: summary, rawOutput: output, summary, tokenCount };
|
|
2195
|
+
}
|
|
2196
|
+
if (retentionPolicy === "summarize_archive") {
|
|
2197
|
+
return {
|
|
2198
|
+
classification,
|
|
2199
|
+
retentionPolicy,
|
|
2200
|
+
content: `ARCHIVED_TOOL_RESPONSE: ${summary}`,
|
|
2201
|
+
rawOutput: output,
|
|
2202
|
+
summary,
|
|
2203
|
+
tokenCount
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
return {
|
|
2207
|
+
classification,
|
|
2208
|
+
retentionPolicy,
|
|
2209
|
+
content: `DROPPED_TOOL_RESPONSE: ${summary}`,
|
|
2210
|
+
rawOutput: output,
|
|
2211
|
+
summary,
|
|
2212
|
+
tokenCount
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
function validateToolArgs(tool, args) {
|
|
2216
|
+
const schema = tool.schema;
|
|
2217
|
+
if (!schema || typeof schema.parse !== "function") {
|
|
2218
|
+
return { ok: true, value: args };
|
|
2219
|
+
}
|
|
2220
|
+
try {
|
|
2221
|
+
return { ok: true, value: schema.parse(args) };
|
|
2222
|
+
} catch (error) {
|
|
2223
|
+
if (error instanceof ZodError) {
|
|
2224
|
+
const message = error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
|
|
2225
|
+
return { ok: false, message };
|
|
2226
|
+
}
|
|
2227
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
// src/nodes/tools.ts
|
|
2232
|
+
function normalizeMaxExecutionsPerRun(value) {
|
|
2233
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
2234
|
+
const parsed = Number(value);
|
|
2235
|
+
if (!Number.isFinite(parsed)) return null;
|
|
2236
|
+
const normalized = Math.trunc(parsed);
|
|
2237
|
+
return normalized >= 0 ? normalized : null;
|
|
2238
|
+
}
|
|
2239
|
+
function countSuccessfulToolExecutions(toolHistory, toolName) {
|
|
2240
|
+
if (!Array.isArray(toolHistory)) return 0;
|
|
2241
|
+
return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
|
|
2242
|
+
}
|
|
1463
2243
|
function createToolsNode(initialTools, opts) {
|
|
1464
|
-
const baseToolByName = /* @__PURE__ */ new Map();
|
|
1465
|
-
for (const t of initialTools) baseToolByName.set(t.name, t);
|
|
1466
2244
|
return async (state) => {
|
|
1467
2245
|
const runtime = state.agent || {
|
|
1468
2246
|
name: opts?.name,
|
|
@@ -1470,15 +2248,17 @@ function createToolsNode(initialTools, opts) {
|
|
|
1470
2248
|
tools: initialTools,
|
|
1471
2249
|
limits: opts?.limits,
|
|
1472
2250
|
systemPrompt: opts?.systemPrompt,
|
|
2251
|
+
todoListPrompt: opts?.todoListPrompt,
|
|
1473
2252
|
useTodoList: opts?.useTodoList,
|
|
1474
2253
|
outputSchema: opts?.outputSchema
|
|
1475
2254
|
};
|
|
2255
|
+
const resolved = getResolvedSmartConfig(opts || {}, runtime);
|
|
1476
2256
|
const activeTools = runtime.tools;
|
|
1477
2257
|
const toolByName = /* @__PURE__ */ new Map();
|
|
1478
|
-
for (const
|
|
2258
|
+
for (const tool of activeTools) toolByName.set(tool.name, tool);
|
|
1479
2259
|
const limits = {
|
|
1480
|
-
maxToolCalls: runtime.limits?.maxToolCalls ?? 10,
|
|
1481
|
-
maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? 1)
|
|
2260
|
+
maxToolCalls: runtime.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10,
|
|
2261
|
+
maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? resolved.limits.maxParallelTools ?? 1)
|
|
1482
2262
|
};
|
|
1483
2263
|
const appended = [];
|
|
1484
2264
|
const onEvent = state.ctx?.__onEvent;
|
|
@@ -1489,20 +2269,19 @@ function createToolsNode(initialTools, opts) {
|
|
|
1489
2269
|
const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
|
|
1490
2270
|
const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
|
|
1491
2271
|
let awaitingApproval = false;
|
|
1492
|
-
for (const
|
|
1493
|
-
const
|
|
1494
|
-
if (
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
2272
|
+
for (const tool of toolByName.values()) {
|
|
2273
|
+
const anyTool = tool;
|
|
2274
|
+
if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
|
|
2275
|
+
anyTool._stateRef.toolHistory = state.toolHistory;
|
|
2276
|
+
anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
|
|
2277
|
+
anyTool._stateRef.__onEvent = onEvent;
|
|
1498
2278
|
}
|
|
1499
2279
|
}
|
|
1500
2280
|
const last = state.messages[state.messages.length - 1];
|
|
1501
2281
|
let toolCount = state.toolCallCount || 0;
|
|
1502
|
-
const toolCalls = Array.isArray(
|
|
1503
|
-
last?.tool_calls
|
|
1504
|
-
) ? last.tool_calls : [];
|
|
2282
|
+
const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
|
|
1505
2283
|
const toolHistory = state.toolHistory || [];
|
|
2284
|
+
const toolHistoryArchived = state.toolHistoryArchived || [];
|
|
1506
2285
|
const remaining = Math.max(0, limits.maxToolCalls - toolCount);
|
|
1507
2286
|
const planned = toolCalls.slice(0, remaining);
|
|
1508
2287
|
const skipped = toolCalls.slice(remaining);
|
|
@@ -1511,6 +2290,17 @@ function createToolsNode(initialTools, opts) {
|
|
|
1511
2290
|
if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
|
|
1512
2291
|
return { cancelled: false, reason: void 0 };
|
|
1513
2292
|
};
|
|
2293
|
+
const markToolFailure = (message, toolName, toolCallId) => {
|
|
2294
|
+
const ctx = { ...state.ctx || {} };
|
|
2295
|
+
ctx.__lastToolError = message;
|
|
2296
|
+
if (toolCallId) {
|
|
2297
|
+
ctx.__toolSchemaError = { toolName, toolCallId, message };
|
|
2298
|
+
}
|
|
2299
|
+
if (resolved.watchdog.autoReplanOnFailure) {
|
|
2300
|
+
ctx.__planNeedsReplan = true;
|
|
2301
|
+
}
|
|
2302
|
+
state.ctx = ctx;
|
|
2303
|
+
};
|
|
1514
2304
|
const runOne = async (tc) => {
|
|
1515
2305
|
const cancelState = isCancelled();
|
|
1516
2306
|
if (cancelState.cancelled) {
|
|
@@ -1520,15 +2310,10 @@ function createToolsNode(initialTools, opts) {
|
|
|
1520
2310
|
onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
|
|
1521
2311
|
return { status: "error" };
|
|
1522
2312
|
}
|
|
1523
|
-
const
|
|
1524
|
-
if (!
|
|
2313
|
+
const tool = toolByName.get(tc.name);
|
|
2314
|
+
if (!tool) {
|
|
2315
|
+
appended.push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1525
2316
|
onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
|
|
1526
|
-
appended.push({
|
|
1527
|
-
role: "tool",
|
|
1528
|
-
content: `Tool not found: ${tc.name}`,
|
|
1529
|
-
tool_call_id: tc.id || `${tc.name}_${appended.length}`,
|
|
1530
|
-
name: tc.name
|
|
1531
|
-
});
|
|
1532
2317
|
toolCount += 1;
|
|
1533
2318
|
return { status: "error" };
|
|
1534
2319
|
}
|
|
@@ -1536,22 +2321,67 @@ function createToolsNode(initialTools, opts) {
|
|
|
1536
2321
|
if (typeof args === "string") {
|
|
1537
2322
|
try {
|
|
1538
2323
|
args = JSON.parse(args);
|
|
1539
|
-
} catch
|
|
2324
|
+
} catch {
|
|
1540
2325
|
}
|
|
1541
2326
|
}
|
|
1542
2327
|
const toolCallId = tc.id || `${tc.name}_${toolCount + 1}`;
|
|
2328
|
+
const validatedArgs = validateToolArgs(tool, args);
|
|
2329
|
+
if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
|
|
2330
|
+
const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
|
|
2331
|
+
appended.push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
|
|
2332
|
+
markToolFailure(errorMessage, tc.name, toolCallId);
|
|
2333
|
+
onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
|
|
2334
|
+
toolCount += 1;
|
|
2335
|
+
return { status: "error" };
|
|
2336
|
+
}
|
|
2337
|
+
if (validatedArgs.ok) {
|
|
2338
|
+
args = validatedArgs.value;
|
|
2339
|
+
}
|
|
2340
|
+
const toolName = tool.name || tc.name;
|
|
2341
|
+
const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
|
|
2342
|
+
if (maxExecutionsPerRun !== null) {
|
|
2343
|
+
const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
|
|
2344
|
+
if (currentExecutions >= maxExecutionsPerRun) {
|
|
2345
|
+
const limitMessage = `Skipped tool due to per-tool execution limit: ${toolName} (${maxExecutionsPerRun}/run)`;
|
|
2346
|
+
appended.push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
|
|
2347
|
+
onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args });
|
|
2348
|
+
recordTraceEvent(traceSession, {
|
|
2349
|
+
type: "tool_call",
|
|
2350
|
+
label: `Tool Skipped - ${toolName}`,
|
|
2351
|
+
actor: { scope: "tool", name: toolName, role: "tool" },
|
|
2352
|
+
status: "skipped",
|
|
2353
|
+
toolExecutionId: tc.id,
|
|
2354
|
+
messageList: [
|
|
2355
|
+
{
|
|
2356
|
+
role: "assistant",
|
|
2357
|
+
name: toolName,
|
|
2358
|
+
content: limitMessage,
|
|
2359
|
+
tool_calls: [
|
|
2360
|
+
{
|
|
2361
|
+
id: tc.id,
|
|
2362
|
+
type: "function",
|
|
2363
|
+
function: { name: toolName, arguments: sanitizeTracePayload(args) }
|
|
2364
|
+
}
|
|
2365
|
+
]
|
|
2366
|
+
}
|
|
2367
|
+
]
|
|
2368
|
+
});
|
|
2369
|
+
toolCount += 1;
|
|
2370
|
+
return { status: "error" };
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
1543
2373
|
let approvalEntry = pendingByCallId.get(toolCallId);
|
|
1544
|
-
const needsApproval = Boolean(
|
|
2374
|
+
const needsApproval = Boolean(tool.needsApproval);
|
|
1545
2375
|
if (needsApproval) {
|
|
1546
2376
|
if (!approvalEntry) {
|
|
1547
2377
|
approvalEntry = {
|
|
1548
2378
|
id: nanoid(),
|
|
1549
2379
|
toolCallId,
|
|
1550
|
-
toolName:
|
|
2380
|
+
toolName: tool.name || tc.name,
|
|
1551
2381
|
args,
|
|
1552
2382
|
status: "pending",
|
|
1553
2383
|
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1554
|
-
metadata:
|
|
2384
|
+
metadata: tool.approvalPrompt || tool.approvalDefaults ? { prompt: tool.approvalPrompt, defaults: tool.approvalDefaults } : void 0
|
|
1555
2385
|
};
|
|
1556
2386
|
pendingApprovals.push(approvalEntry);
|
|
1557
2387
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
@@ -1573,71 +2403,97 @@ function createToolsNode(initialTools, opts) {
|
|
|
1573
2403
|
}
|
|
1574
2404
|
if (approvalEntry.status === "rejected") {
|
|
1575
2405
|
const rejectionMessage = approvalEntry.comment || "Tool call rejected by reviewer.";
|
|
1576
|
-
appended.push({
|
|
1577
|
-
role: "tool",
|
|
1578
|
-
content: `Tool call rejected: ${rejectionMessage}`,
|
|
1579
|
-
tool_call_id: toolCallId,
|
|
1580
|
-
name: tc.name
|
|
1581
|
-
});
|
|
1582
|
-
onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
|
|
2406
|
+
appended.push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
|
|
1583
2407
|
approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
|
|
1584
2408
|
approvalEntry.status = "executed";
|
|
1585
2409
|
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1586
|
-
toolHistory.push({ executionId: nanoid(), toolName: t.name, args, output: `Rejected: ${rejectionMessage}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
|
|
1587
2410
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
2411
|
+
toolHistory.push({ executionId: nanoid(), toolName: tool.name, args, output: `Rejected: ${rejectionMessage}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "rejected" });
|
|
2412
|
+
onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
|
|
1588
2413
|
toolCount += 1;
|
|
1589
2414
|
return { status: "rejected", approval: approvalEntry };
|
|
1590
2415
|
}
|
|
1591
|
-
if (approvalEntry.status === "approved") {
|
|
1592
|
-
|
|
1593
|
-
args = approvalEntry.approvedArgs;
|
|
1594
|
-
}
|
|
2416
|
+
if (approvalEntry.status === "approved" && approvalEntry.approvedArgs !== void 0) {
|
|
2417
|
+
args = approvalEntry.approvedArgs;
|
|
1595
2418
|
onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
|
|
1596
2419
|
}
|
|
1597
2420
|
}
|
|
1598
2421
|
const start = Date.now();
|
|
2422
|
+
const sanitizedArgs = sanitizeTracePayload(args);
|
|
2423
|
+
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1599
2424
|
try {
|
|
1600
|
-
onEvent?.({ type: "tool_call", phase: "start", name:
|
|
2425
|
+
onEvent?.({ type: "tool_call", phase: "start", name: toolName, id: tc.id, args });
|
|
1601
2426
|
onProgress?.({ stage: "tools", message: `Running tool ${tc.name}` });
|
|
1602
|
-
const
|
|
1603
|
-
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1604
|
-
let output;
|
|
1605
|
-
const anyTool = t;
|
|
2427
|
+
const anyTool = tool;
|
|
1606
2428
|
const callOptions = { cancellationToken, signal: abortSignal };
|
|
2429
|
+
let output;
|
|
1607
2430
|
if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
|
|
1608
2431
|
else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
|
|
1609
2432
|
else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
|
|
1610
2433
|
else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
|
|
1611
2434
|
else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
|
|
1612
2435
|
else throw new Error("Tool is not invokable");
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
toolHistory.push({ executionId
|
|
2436
|
+
const durationMs = Date.now() - start;
|
|
2437
|
+
const executionId = nanoid();
|
|
2438
|
+
if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
|
|
2439
|
+
toolHistory.push({ executionId, toolName, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "handoff" });
|
|
1617
2440
|
appended.push({ role: "tool", content: "ok", tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1618
|
-
|
|
1619
|
-
state.agent
|
|
1620
|
-
onEvent?.({ type: "tool_call", phase: "success", name:
|
|
1621
|
-
toolCount += 1;
|
|
2441
|
+
state.agent = output.__handoff.runtime;
|
|
2442
|
+
onEvent?.({ type: "handoff", from: runtime.name, to: state.agent?.name, toolName });
|
|
2443
|
+
onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: "handoff", durationMs });
|
|
1622
2444
|
if (needsApproval && approvalEntry) {
|
|
1623
2445
|
approvalEntry.status = "executed";
|
|
1624
2446
|
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1625
|
-
approvalEntry.executionId =
|
|
2447
|
+
approvalEntry.executionId = executionId;
|
|
1626
2448
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
1627
2449
|
}
|
|
2450
|
+
toolCount += 1;
|
|
1628
2451
|
return { status: "success", approval: approvalEntry };
|
|
1629
2452
|
}
|
|
1630
|
-
const content = typeof output === "string" ? output : JSON.stringify(output);
|
|
1631
2453
|
if (output && typeof output === "object" && output.__finalStructuredOutput) {
|
|
1632
2454
|
if (!state.ctx) state.ctx = {};
|
|
1633
2455
|
state.ctx.__structuredOutputParsed = output.data;
|
|
1634
2456
|
state.ctx.__finalizedDueToStructuredOutput = true;
|
|
1635
2457
|
}
|
|
1636
|
-
const
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
2458
|
+
const responsePolicy = resolveToolResponsePolicy(toolName, output, resolved);
|
|
2459
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2460
|
+
const storedOutput = responsePolicy.retentionPolicy === "keep_full" ? output : responsePolicy.summary;
|
|
2461
|
+
toolHistory.push({
|
|
2462
|
+
executionId,
|
|
2463
|
+
toolName,
|
|
2464
|
+
args,
|
|
2465
|
+
output: storedOutput,
|
|
2466
|
+
rawOutput: output,
|
|
2467
|
+
timestamp,
|
|
2468
|
+
tool_call_id: tc.id,
|
|
2469
|
+
summarized: responsePolicy.retentionPolicy !== "keep_full",
|
|
2470
|
+
originalTokenCount: responsePolicy.tokenCount,
|
|
2471
|
+
classification: responsePolicy.classification,
|
|
2472
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2473
|
+
summary: responsePolicy.summary,
|
|
2474
|
+
archiveId: responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop" ? executionId : void 0,
|
|
2475
|
+
status: "success"
|
|
2476
|
+
});
|
|
2477
|
+
if (resolved.context.archiveLargeToolResponses && (responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop")) {
|
|
2478
|
+
toolHistoryArchived.push({
|
|
2479
|
+
executionId,
|
|
2480
|
+
toolName,
|
|
2481
|
+
args,
|
|
2482
|
+
output,
|
|
2483
|
+
rawOutput: output,
|
|
2484
|
+
timestamp,
|
|
2485
|
+
tool_call_id: tc.id,
|
|
2486
|
+
summarized: true,
|
|
2487
|
+
originalTokenCount: responsePolicy.tokenCount,
|
|
2488
|
+
classification: responsePolicy.classification,
|
|
2489
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2490
|
+
summary: responsePolicy.summary,
|
|
2491
|
+
archiveId: executionId,
|
|
2492
|
+
status: "success"
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
2496
|
+
onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
|
|
1641
2497
|
onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
|
|
1642
2498
|
if (needsApproval && approvalEntry) {
|
|
1643
2499
|
approvalEntry.status = "executed";
|
|
@@ -1647,7 +2503,6 @@ function createToolsNode(initialTools, opts) {
|
|
|
1647
2503
|
}
|
|
1648
2504
|
const sanitizedOutput = sanitizeTracePayload(output);
|
|
1649
2505
|
const outputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedOutput) : void 0;
|
|
1650
|
-
const toolName = t.name || tc.name;
|
|
1651
2506
|
const messageList = [
|
|
1652
2507
|
{
|
|
1653
2508
|
role: "assistant",
|
|
@@ -1667,7 +2522,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1667
2522
|
{
|
|
1668
2523
|
role: "tool",
|
|
1669
2524
|
name: toolName,
|
|
1670
|
-
content:
|
|
2525
|
+
content: responsePolicy.content
|
|
1671
2526
|
}
|
|
1672
2527
|
];
|
|
1673
2528
|
recordTraceEvent(traceSession, {
|
|
@@ -1678,43 +2533,24 @@ function createToolsNode(initialTools, opts) {
|
|
|
1678
2533
|
requestBytes: inputBytes,
|
|
1679
2534
|
responseBytes: outputBytes,
|
|
1680
2535
|
toolExecutionId: executionId,
|
|
1681
|
-
messageList
|
|
2536
|
+
messageList,
|
|
2537
|
+
debug: {
|
|
2538
|
+
classification: responsePolicy.classification,
|
|
2539
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2540
|
+
originalTokenCount: responsePolicy.tokenCount
|
|
2541
|
+
}
|
|
1682
2542
|
});
|
|
1683
2543
|
toolCount += 1;
|
|
1684
2544
|
return { status: "success", approval: approvalEntry };
|
|
1685
|
-
} catch (
|
|
2545
|
+
} catch (error) {
|
|
1686
2546
|
const durationMs = Date.now() - start;
|
|
1687
2547
|
const executionId = nanoid();
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
const toolName = t.name || tc.name;
|
|
1695
|
-
const errorMessage = e?.message || String(e);
|
|
1696
|
-
const messageList = [
|
|
1697
|
-
{
|
|
1698
|
-
role: "assistant",
|
|
1699
|
-
name: toolName,
|
|
1700
|
-
content: "",
|
|
1701
|
-
tool_calls: [
|
|
1702
|
-
{
|
|
1703
|
-
id: tc.id || executionId,
|
|
1704
|
-
type: "function",
|
|
1705
|
-
function: {
|
|
1706
|
-
name: toolName,
|
|
1707
|
-
arguments: sanitizedArgs
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
]
|
|
1711
|
-
},
|
|
1712
|
-
{
|
|
1713
|
-
role: "tool",
|
|
1714
|
-
name: toolName,
|
|
1715
|
-
content: `Error executing tool: ${errorMessage}`
|
|
1716
|
-
}
|
|
1717
|
-
];
|
|
2548
|
+
const message = error?.message || String(error);
|
|
2549
|
+
toolHistory.push({ executionId, toolName, args, output: `Error executing tool: ${message}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "error" });
|
|
2550
|
+
appended.push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
2551
|
+
markToolFailure(message, toolName, toolCallId);
|
|
2552
|
+
onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
|
|
2553
|
+
onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
|
|
1718
2554
|
recordTraceEvent(traceSession, {
|
|
1719
2555
|
type: "tool_call",
|
|
1720
2556
|
label: `Tool Error - ${toolName}`,
|
|
@@ -1723,8 +2559,26 @@ function createToolsNode(initialTools, opts) {
|
|
|
1723
2559
|
durationMs,
|
|
1724
2560
|
requestBytes: inputBytes,
|
|
1725
2561
|
toolExecutionId: executionId,
|
|
1726
|
-
error: { message
|
|
1727
|
-
messageList
|
|
2562
|
+
error: { message, stack: error?.stack },
|
|
2563
|
+
messageList: [
|
|
2564
|
+
{
|
|
2565
|
+
role: "assistant",
|
|
2566
|
+
name: toolName,
|
|
2567
|
+
content: "",
|
|
2568
|
+
tool_calls: [
|
|
2569
|
+
{
|
|
2570
|
+
id: tc.id || executionId,
|
|
2571
|
+
type: "function",
|
|
2572
|
+
function: { name: toolName, arguments: sanitizedArgs }
|
|
2573
|
+
}
|
|
2574
|
+
]
|
|
2575
|
+
},
|
|
2576
|
+
{
|
|
2577
|
+
role: "tool",
|
|
2578
|
+
name: toolName,
|
|
2579
|
+
content: `Error executing tool: ${message}`
|
|
2580
|
+
}
|
|
2581
|
+
]
|
|
1728
2582
|
});
|
|
1729
2583
|
toolCount += 1;
|
|
1730
2584
|
return { status: "error", approval: approvalEntry };
|
|
@@ -1741,7 +2595,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1741
2595
|
break;
|
|
1742
2596
|
}
|
|
1743
2597
|
const result = await runOne(tc);
|
|
1744
|
-
if (result
|
|
2598
|
+
if (result.status === "awaiting_approval") {
|
|
1745
2599
|
awaitingApproval = true;
|
|
1746
2600
|
break;
|
|
1747
2601
|
}
|
|
@@ -1749,37 +2603,28 @@ function createToolsNode(initialTools, opts) {
|
|
|
1749
2603
|
for (const tc of skipped) {
|
|
1750
2604
|
onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
|
|
1751
2605
|
const sanitizedArgs = sanitizeTracePayload(tc.args);
|
|
1752
|
-
const messageList = [
|
|
1753
|
-
{
|
|
1754
|
-
role: "assistant",
|
|
1755
|
-
name: tc.name,
|
|
1756
|
-
content: `Skipped tool due to tool-call limit: ${tc.name}`,
|
|
1757
|
-
tool_calls: [
|
|
1758
|
-
{
|
|
1759
|
-
id: tc.id,
|
|
1760
|
-
type: "function",
|
|
1761
|
-
function: {
|
|
1762
|
-
name: tc.name,
|
|
1763
|
-
arguments: sanitizedArgs
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
]
|
|
1767
|
-
}
|
|
1768
|
-
];
|
|
1769
2606
|
recordTraceEvent(traceSession, {
|
|
1770
2607
|
type: "tool_call",
|
|
1771
2608
|
label: `Tool Skipped - ${tc.name}`,
|
|
1772
2609
|
actor: { scope: "tool", name: tc.name, role: "tool" },
|
|
1773
2610
|
status: "skipped",
|
|
1774
2611
|
toolExecutionId: tc.id,
|
|
1775
|
-
messageList
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
2612
|
+
messageList: [
|
|
2613
|
+
{
|
|
2614
|
+
role: "assistant",
|
|
2615
|
+
name: tc.name,
|
|
2616
|
+
content: `Skipped tool due to tool-call limit: ${tc.name}`,
|
|
2617
|
+
tool_calls: [
|
|
2618
|
+
{
|
|
2619
|
+
id: tc.id,
|
|
2620
|
+
type: "function",
|
|
2621
|
+
function: { name: tc.name, arguments: sanitizedArgs }
|
|
2622
|
+
}
|
|
2623
|
+
]
|
|
2624
|
+
}
|
|
2625
|
+
]
|
|
1782
2626
|
});
|
|
2627
|
+
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 });
|
|
1783
2628
|
toolCount += 1;
|
|
1784
2629
|
}
|
|
1785
2630
|
if (!awaitingApproval && state.ctx?.__awaitingApproval) {
|
|
@@ -1794,6 +2639,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1794
2639
|
messages: [...state.messages, ...appended],
|
|
1795
2640
|
toolCallCount: toolCount,
|
|
1796
2641
|
toolHistory,
|
|
2642
|
+
toolHistoryArchived,
|
|
1797
2643
|
agent: state.agent,
|
|
1798
2644
|
pendingApprovals
|
|
1799
2645
|
};
|
|
@@ -1918,7 +2764,15 @@ function createGuardrail(options) {
|
|
|
1918
2764
|
}
|
|
1919
2765
|
|
|
1920
2766
|
// src/utils/stateSnapshot.ts
|
|
1921
|
-
var DISALLOWED_CTX_KEYS = /* @__PURE__ */ new Set([
|
|
2767
|
+
var DISALLOWED_CTX_KEYS = /* @__PURE__ */ new Set([
|
|
2768
|
+
"__onEvent",
|
|
2769
|
+
"__onProgress",
|
|
2770
|
+
"__onStream",
|
|
2771
|
+
"__traceSession",
|
|
2772
|
+
"__paused",
|
|
2773
|
+
"__cancellationToken",
|
|
2774
|
+
"__abortSignal"
|
|
2775
|
+
]);
|
|
1922
2776
|
var clone = (value) => {
|
|
1923
2777
|
if (typeof globalThis.structuredClone === "function") {
|
|
1924
2778
|
return globalThis.structuredClone(value);
|
|
@@ -2010,68 +2864,20 @@ function resolveToolApprovalState(state, resolution) {
|
|
|
2010
2864
|
};
|
|
2011
2865
|
}
|
|
2012
2866
|
|
|
2013
|
-
// src/
|
|
2014
|
-
function
|
|
2015
|
-
if (!
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
|
|
2019
|
-
}
|
|
2020
|
-
function extractMessageText(m) {
|
|
2021
|
-
if (!m) return "";
|
|
2022
|
-
const parts = [];
|
|
2023
|
-
parts.push(m.role || "");
|
|
2024
|
-
if (m.name) parts.push(m.name);
|
|
2025
|
-
if (typeof m.content === "string") {
|
|
2026
|
-
parts.push(m.content);
|
|
2027
|
-
} else if (Array.isArray(m.content)) {
|
|
2028
|
-
for (const c of m.content) {
|
|
2029
|
-
if (typeof c === "string") parts.push(c);
|
|
2030
|
-
else if (c?.text) parts.push(c.text);
|
|
2031
|
-
else if (c?.content) parts.push(c.content);
|
|
2032
|
-
else if (c?.type === "tool_use" && c?.input) {
|
|
2033
|
-
parts.push(c.name || "");
|
|
2034
|
-
parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
|
|
2035
|
-
} else if (c != null && typeof c === "object") {
|
|
2036
|
-
parts.push(JSON.stringify(c));
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
} else if (m.content != null && typeof m.content === "object") {
|
|
2040
|
-
parts.push(JSON.stringify(m.content));
|
|
2041
|
-
}
|
|
2042
|
-
if (Array.isArray(m.tool_calls)) {
|
|
2043
|
-
for (const tc of m.tool_calls) {
|
|
2044
|
-
if (tc?.function?.name) parts.push(tc.function.name);
|
|
2045
|
-
if (tc?.function?.arguments) {
|
|
2046
|
-
parts.push(
|
|
2047
|
-
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2048
|
-
);
|
|
2049
|
-
}
|
|
2050
|
-
if (tc?.name && !tc?.function?.name) parts.push(tc.name);
|
|
2051
|
-
if (tc?.args && !tc?.function?.arguments) {
|
|
2052
|
-
parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2867
|
+
// src/agent.ts
|
|
2868
|
+
function isSyntheticSummaryMessage(message) {
|
|
2869
|
+
if (!message) return false;
|
|
2870
|
+
if (message.role === "tool" && message.name === "summarize_context") {
|
|
2871
|
+
return true;
|
|
2055
2872
|
}
|
|
2056
|
-
if (Array.isArray(
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2062
|
-
);
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2873
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
2874
|
+
return message.tool_calls.some((toolCall) => {
|
|
2875
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
2876
|
+
return toolName === "summarize_context";
|
|
2877
|
+
});
|
|
2065
2878
|
}
|
|
2066
|
-
return
|
|
2067
|
-
}
|
|
2068
|
-
function countMessagesTokens(messages) {
|
|
2069
|
-
if (!Array.isArray(messages) || messages.length === 0) return 0;
|
|
2070
|
-
const allText = messages.map(extractMessageText).join("\n");
|
|
2071
|
-
return countApproxTokens(allText) + messages.length * 4;
|
|
2879
|
+
return false;
|
|
2072
2880
|
}
|
|
2073
|
-
|
|
2074
|
-
// src/agent.ts
|
|
2075
2881
|
function createAgent(opts) {
|
|
2076
2882
|
const resolver = createResolverNode();
|
|
2077
2883
|
const agentCore = createAgentCoreNode(opts);
|
|
@@ -2114,6 +2920,7 @@ function createAgent(opts) {
|
|
|
2114
2920
|
tools: toolsBase,
|
|
2115
2921
|
guardrails: opts.guardrails,
|
|
2116
2922
|
systemPrompt: void 0,
|
|
2923
|
+
todoListPrompt: opts.todoListPrompt,
|
|
2117
2924
|
limits: opts.limits,
|
|
2118
2925
|
useTodoList: void 0,
|
|
2119
2926
|
outputSchema: opts.outputSchema,
|
|
@@ -2189,6 +2996,18 @@ function createAgent(opts) {
|
|
|
2189
2996
|
};
|
|
2190
2997
|
while (iterations < iterationLimit) {
|
|
2191
2998
|
iterations++;
|
|
2999
|
+
const traceSession = state.ctx?.__traceSession;
|
|
3000
|
+
if (traceSession) {
|
|
3001
|
+
const iterEvent = recordTraceEvent(traceSession, {
|
|
3002
|
+
type: "agent_iteration",
|
|
3003
|
+
label: `Iteration ${iterations}`,
|
|
3004
|
+
actor: { scope: "agent", name: opts.name || "agent", role: "orchestrator" },
|
|
3005
|
+
parentSpanId: traceSession.rootSpanId
|
|
3006
|
+
});
|
|
3007
|
+
if (iterEvent) {
|
|
3008
|
+
traceSession.currentIterationSpanId = iterEvent.spanId;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
2192
3011
|
if (cancelIfRequested("loop")) break;
|
|
2193
3012
|
const skippingAgent = resumeStage === "tools";
|
|
2194
3013
|
if (!skippingAgent) {
|
|
@@ -2239,11 +3058,21 @@ function createAgent(opts) {
|
|
|
2239
3058
|
maxTok = 5e4;
|
|
2240
3059
|
}
|
|
2241
3060
|
if (maxTok) {
|
|
2242
|
-
const
|
|
3061
|
+
const tokCountMessages = (state.messages || []).filter((message) => {
|
|
3062
|
+
if (isSyntheticSummaryMessage(message)) return false;
|
|
3063
|
+
if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
|
|
3064
|
+
return true;
|
|
3065
|
+
});
|
|
3066
|
+
const tokenCount = countMessagesTokens(tokCountMessages);
|
|
2243
3067
|
if (tokenCount > maxTok) {
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
|
|
3068
|
+
const hasFreshSummary = (state.messages || []).some(
|
|
3069
|
+
(m) => m.role === "tool" && m.content === "SUMMARIZED"
|
|
3070
|
+
);
|
|
3071
|
+
if (hasFreshSummary && tokenCount <= maxTok * 1.15) ; else {
|
|
3072
|
+
const ctx = { ...state.ctx || {}, __needsSummarization: true };
|
|
3073
|
+
state = { ...state, ctx };
|
|
3074
|
+
break;
|
|
3075
|
+
}
|
|
2247
3076
|
}
|
|
2248
3077
|
}
|
|
2249
3078
|
onProgress?.({ stage: "agent", message: "Invoking model" });
|
|
@@ -2481,7 +3310,11 @@ function createAgent(opts) {
|
|
|
2481
3310
|
schema,
|
|
2482
3311
|
func: async ({ input }) => {
|
|
2483
3312
|
const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
|
|
2484
|
-
return {
|
|
3313
|
+
return {
|
|
3314
|
+
content: res.content,
|
|
3315
|
+
output: res.output,
|
|
3316
|
+
summary: res.state?.summaries?.[res.state.summaries.length - 1]
|
|
3317
|
+
};
|
|
2485
3318
|
}
|
|
2486
3319
|
});
|
|
2487
3320
|
},
|
|
@@ -2522,6 +3355,7 @@ function createAgentNode(opts) {
|
|
|
2522
3355
|
model: opts.model,
|
|
2523
3356
|
tools: opts.tools || [],
|
|
2524
3357
|
systemPrompt: opts.systemPrompt,
|
|
3358
|
+
todoListPrompt: opts.todoListPrompt,
|
|
2525
3359
|
limits: opts.limits,
|
|
2526
3360
|
useTodoList: opts.useTodoList,
|
|
2527
3361
|
outputSchema: opts.outputSchema
|
|
@@ -2539,7 +3373,8 @@ function createAgentNode(opts) {
|
|
|
2539
3373
|
content: buildSystemPrompt(
|
|
2540
3374
|
[runtime.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
|
|
2541
3375
|
runtime.useTodoList === true,
|
|
2542
|
-
runtime.name || "Agent"
|
|
3376
|
+
runtime.name || "Agent",
|
|
3377
|
+
runtime.todoListPrompt
|
|
2543
3378
|
)
|
|
2544
3379
|
};
|
|
2545
3380
|
const messages = [systemMsg, ...state.messages];
|
|
@@ -2621,9 +3456,119 @@ function createAgentNode(opts) {
|
|
|
2621
3456
|
};
|
|
2622
3457
|
}
|
|
2623
3458
|
|
|
3459
|
+
// src/smart/contextPolicy.ts
|
|
3460
|
+
function messageText(message) {
|
|
3461
|
+
if (typeof message.content === "string") return message.content;
|
|
3462
|
+
if (Array.isArray(message.content)) {
|
|
3463
|
+
return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? JSON.stringify(part)).join(" ");
|
|
3464
|
+
}
|
|
3465
|
+
return "";
|
|
3466
|
+
}
|
|
3467
|
+
function collectRecentTurns(messages, lastTurnsToKeep) {
|
|
3468
|
+
if (lastTurnsToKeep <= 0) return [];
|
|
3469
|
+
const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
|
|
3470
|
+
const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
|
|
3471
|
+
let userTurnCount = 0;
|
|
3472
|
+
const collected = [];
|
|
3473
|
+
for (let index = body.length - 1; index >= 0; index -= 1) {
|
|
3474
|
+
const message = body[index];
|
|
3475
|
+
collected.unshift(message);
|
|
3476
|
+
if (message.role === "user") {
|
|
3477
|
+
userTurnCount += 1;
|
|
3478
|
+
if (userTurnCount >= lastTurnsToKeep) break;
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
return [...systemPrefix, ...collected];
|
|
3482
|
+
}
|
|
3483
|
+
function renderStructuredSummary(summary) {
|
|
3484
|
+
if (!summary) return "";
|
|
3485
|
+
const lines = ["Context summary:"];
|
|
3486
|
+
if (summary.stable_facts.length > 0) {
|
|
3487
|
+
lines.push("Stable facts:");
|
|
3488
|
+
lines.push(...summary.stable_facts.map((fact) => `- ${fact.key}: ${fact.value}`));
|
|
3489
|
+
}
|
|
3490
|
+
if (summary.active_goals.length > 0) {
|
|
3491
|
+
lines.push("Active goals:");
|
|
3492
|
+
lines.push(...summary.active_goals.map((goal) => `- ${goal}`));
|
|
3493
|
+
}
|
|
3494
|
+
if (summary.open_questions.length > 0) {
|
|
3495
|
+
lines.push("Open questions:");
|
|
3496
|
+
lines.push(...summary.open_questions.map((question) => `- ${question}`));
|
|
3497
|
+
}
|
|
3498
|
+
if (summary.discarded_obsolete.length > 0) {
|
|
3499
|
+
lines.push("Discarded obsolete:");
|
|
3500
|
+
lines.push(...summary.discarded_obsolete.map((item) => `- ${item}`));
|
|
3501
|
+
}
|
|
3502
|
+
return lines.join("\n");
|
|
3503
|
+
}
|
|
3504
|
+
function renderMemoryBlock(facts) {
|
|
3505
|
+
if (!facts || facts.length === 0) return "";
|
|
3506
|
+
return [
|
|
3507
|
+
"Retrieved memory:",
|
|
3508
|
+
...facts.map((fact) => `- ${fact.key}: ${fact.value}`)
|
|
3509
|
+
].join("\n");
|
|
3510
|
+
}
|
|
3511
|
+
function clampToBudget(messages, maxContextTokens) {
|
|
3512
|
+
let working = [...messages];
|
|
3513
|
+
while (working.length > 2) {
|
|
3514
|
+
const tokenCount = countApproxTokens(working.map(messageText).join("\n"));
|
|
3515
|
+
if (tokenCount <= maxContextTokens) return working;
|
|
3516
|
+
const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
|
|
3517
|
+
if (firstNonSystem < 0) break;
|
|
3518
|
+
working.splice(firstNonSystem, 1);
|
|
3519
|
+
}
|
|
3520
|
+
return working;
|
|
3521
|
+
}
|
|
3522
|
+
function buildModelMessages(state, config) {
|
|
3523
|
+
const rawMessages = state.messages || [];
|
|
3524
|
+
if (config.context.policy === "raw") {
|
|
3525
|
+
return clampToBudget(rawMessages, config.limits.maxContextTokens);
|
|
3526
|
+
}
|
|
3527
|
+
const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
|
|
3528
|
+
const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
|
|
3529
|
+
const summaryText = renderStructuredSummary(latestSummary);
|
|
3530
|
+
const memoryText = renderMemoryBlock(state.memoryFacts);
|
|
3531
|
+
const systemMessage2 = recentMessages[0]?.role === "system" ? recentMessages[0] : void 0;
|
|
3532
|
+
const body = systemMessage2 ? recentMessages.slice(1) : recentMessages;
|
|
3533
|
+
const syntheticContextMessages = [];
|
|
3534
|
+
if (summaryText) {
|
|
3535
|
+
syntheticContextMessages.push({ role: "system", name: "context_summary", content: summaryText });
|
|
3536
|
+
}
|
|
3537
|
+
if (memoryText) {
|
|
3538
|
+
syntheticContextMessages.push({ role: "system", name: "memory_context", content: memoryText });
|
|
3539
|
+
}
|
|
3540
|
+
const assembled = [
|
|
3541
|
+
...systemMessage2 ? [systemMessage2] : [],
|
|
3542
|
+
...syntheticContextMessages,
|
|
3543
|
+
...config.context.policy === "summary_only" ? body.filter((message) => message.role === "user" || message.role === "assistant").slice(-2) : body
|
|
3544
|
+
];
|
|
3545
|
+
return clampToBudget(assembled, config.limits.maxContextTokens);
|
|
3546
|
+
}
|
|
3547
|
+
function estimateContextRotScore(state) {
|
|
3548
|
+
const summaryCount = state.summaryRecords?.length || 0;
|
|
3549
|
+
const archivedCount = state.toolHistoryArchived?.length || 0;
|
|
3550
|
+
const messageCount = state.messages?.length || 0;
|
|
3551
|
+
const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && message.content === "SUMMARIZED").length || 0;
|
|
3552
|
+
const numerator = summaryCount * 2 + archivedCount + summarizedMessages;
|
|
3553
|
+
const denominator = Math.max(messageCount, 1);
|
|
3554
|
+
return Number((numerator / denominator).toFixed(3));
|
|
3555
|
+
}
|
|
3556
|
+
|
|
2624
3557
|
// src/nodes/contextSummarize.ts
|
|
2625
3558
|
var systemMessage = (content) => ({ role: "system", content });
|
|
2626
3559
|
var humanMessage = (content) => ({ role: "user", content });
|
|
3560
|
+
function isSyntheticSummaryToolMessage(message) {
|
|
3561
|
+
return message.role === "tool" && message.name === "summarize_context";
|
|
3562
|
+
}
|
|
3563
|
+
function isSyntheticSummaryAssistantMessage(message) {
|
|
3564
|
+
if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) {
|
|
3565
|
+
return false;
|
|
3566
|
+
}
|
|
3567
|
+
return message.tool_calls.some((toolCall) => {
|
|
3568
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
3569
|
+
return toolName === "summarize_context";
|
|
3570
|
+
});
|
|
3571
|
+
}
|
|
2627
3572
|
function validateMessageSequence(messages) {
|
|
2628
3573
|
const validated = [];
|
|
2629
3574
|
const pendingToolCallIds = /* @__PURE__ */ new Set();
|
|
@@ -2649,33 +3594,137 @@ function validateMessageSequence(messages) {
|
|
|
2649
3594
|
}
|
|
2650
3595
|
return validated;
|
|
2651
3596
|
}
|
|
2652
|
-
function
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
3597
|
+
function extractJsonObject(text) {
|
|
3598
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
3599
|
+
if (fenced?.[1]) return fenced[1].trim();
|
|
3600
|
+
const start = text.indexOf("{");
|
|
3601
|
+
const end = text.lastIndexOf("}");
|
|
3602
|
+
if (start >= 0 && end > start) {
|
|
3603
|
+
return text.slice(start, end + 1);
|
|
3604
|
+
}
|
|
3605
|
+
return null;
|
|
3606
|
+
}
|
|
3607
|
+
function normalizeStructuredSummary(input, fallbackText) {
|
|
3608
|
+
const summary = {
|
|
3609
|
+
stable_facts: Array.isArray(input?.stable_facts) ? input.stable_facts.filter((fact) => fact && typeof fact === "object").map((fact) => ({
|
|
3610
|
+
key: String(fact.key || "unknown"),
|
|
3611
|
+
value: String(fact.value || ""),
|
|
3612
|
+
confidence: typeof fact.confidence === "number" ? fact.confidence : 0.7,
|
|
3613
|
+
source: fact.source ? String(fact.source) : void 0
|
|
3614
|
+
})) : [],
|
|
3615
|
+
active_goals: Array.isArray(input?.active_goals) ? input.active_goals.map((goal) => String(goal)) : [],
|
|
3616
|
+
open_questions: Array.isArray(input?.open_questions) ? input.open_questions.map((question) => String(question)) : [],
|
|
3617
|
+
discarded_obsolete: Array.isArray(input?.discarded_obsolete || input?.obsolete_discarded) ? (input.discarded_obsolete || input.obsolete_discarded).map((item) => String(item)) : [],
|
|
3618
|
+
rawSummary: typeof input?.rawSummary === "string" ? input.rawSummary : fallbackText
|
|
3619
|
+
};
|
|
3620
|
+
if (summary.stable_facts.length === 0 && fallbackText) {
|
|
3621
|
+
const importantLines = fallbackText.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 8);
|
|
3622
|
+
summary.stable_facts = importantLines.map((line, index) => ({
|
|
3623
|
+
key: `fact_${index + 1}`,
|
|
3624
|
+
value: line,
|
|
3625
|
+
confidence: 0.5
|
|
3626
|
+
}));
|
|
3627
|
+
}
|
|
3628
|
+
return summary;
|
|
3629
|
+
}
|
|
3630
|
+
function normalizeFactKeySegment(value) {
|
|
3631
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "item";
|
|
3632
|
+
}
|
|
3633
|
+
function extractCanonicalFacts(messages) {
|
|
3634
|
+
const facts = /* @__PURE__ */ new Map();
|
|
3635
|
+
for (const message of messages) {
|
|
3636
|
+
const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.map((part) => part?.text ?? part?.content ?? String(part ?? "")).join("\n") : "";
|
|
3637
|
+
if (!content) continue;
|
|
3638
|
+
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3639
|
+
for (const line of lines) {
|
|
3640
|
+
if (!/^[A-Z0-9_]+\|/.test(line) || !line.includes("=")) continue;
|
|
3641
|
+
const segments = line.split("|").map((segment) => segment.trim()).filter(Boolean);
|
|
3642
|
+
const label = segments[0];
|
|
3643
|
+
const entries = segments.slice(1).map((segment) => {
|
|
3644
|
+
const eqIndex = segment.indexOf("=");
|
|
3645
|
+
if (eqIndex <= 0) return null;
|
|
3646
|
+
return {
|
|
3647
|
+
key: segment.slice(0, eqIndex).trim(),
|
|
3648
|
+
value: segment.slice(eqIndex + 1).trim()
|
|
3649
|
+
};
|
|
3650
|
+
}).filter((entry) => Boolean(entry?.key));
|
|
3651
|
+
if (entries.length === 0) continue;
|
|
3652
|
+
const identifier = entries.find((entry) => ["code", "id", "project", "name", "key"].includes(entry.key.toLowerCase()))?.value;
|
|
3653
|
+
const keyPrefix = [normalizeFactKeySegment(label), identifier ? normalizeFactKeySegment(identifier) : void 0].filter(Boolean).join(".");
|
|
3654
|
+
for (const entry of entries) {
|
|
3655
|
+
const normalizedEntryKey = normalizeFactKeySegment(entry.key);
|
|
3656
|
+
const factKey = keyPrefix ? `${keyPrefix}.${normalizedEntryKey}` : `${normalizeFactKeySegment(label)}.${normalizedEntryKey}`;
|
|
3657
|
+
facts.set(factKey, {
|
|
3658
|
+
key: factKey,
|
|
3659
|
+
value: entry.value,
|
|
3660
|
+
confidence: 0.98,
|
|
3661
|
+
source: "canonical_tool_output"
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
return [...facts.values()];
|
|
3667
|
+
}
|
|
3668
|
+
function mergeStableFacts(summary, additionalFacts) {
|
|
3669
|
+
if (additionalFacts.length === 0) return summary;
|
|
3670
|
+
const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
|
|
3671
|
+
for (const fact of additionalFacts) {
|
|
3672
|
+
const existing = merged.get(fact.key);
|
|
3673
|
+
if (!existing || (fact.confidence || 0) >= (existing.confidence || 0)) {
|
|
3674
|
+
merged.set(fact.key, fact);
|
|
3675
|
+
}
|
|
2662
3676
|
}
|
|
2663
3677
|
return {
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
summaryPromptMaxTokens: 8e3,
|
|
2667
|
-
promptTemplate: void 0
|
|
3678
|
+
...summary,
|
|
3679
|
+
stable_facts: [...merged.values()]
|
|
2668
3680
|
};
|
|
2669
3681
|
}
|
|
3682
|
+
function runIntegrityCheck(previous, current) {
|
|
3683
|
+
const notes = [];
|
|
3684
|
+
const previousKeys = new Set((previous?.stable_facts || []).map((fact) => fact.key));
|
|
3685
|
+
const currentKeys = new Set(current.stable_facts.map((fact) => fact.key));
|
|
3686
|
+
const obsoleteKeys = new Set(current.discarded_obsolete);
|
|
3687
|
+
const criticalFactLoss = [...previousKeys].some((key) => !obsoleteKeys.has(key) && !currentKeys.has(key));
|
|
3688
|
+
const obsoleteFactRevived = current.stable_facts.some((fact) => obsoleteKeys.has(fact.key));
|
|
3689
|
+
if (criticalFactLoss) {
|
|
3690
|
+
notes.push("Missing previously retained facts; merged forward during integrity repair.");
|
|
3691
|
+
}
|
|
3692
|
+
if (obsoleteFactRevived) {
|
|
3693
|
+
notes.push("Obsolete facts reappeared; removed during integrity repair.");
|
|
3694
|
+
}
|
|
3695
|
+
return {
|
|
3696
|
+
passed: !criticalFactLoss && !obsoleteFactRevived,
|
|
3697
|
+
criticalFactLoss,
|
|
3698
|
+
obsoleteFactRevived,
|
|
3699
|
+
notes
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
function repairStructuredSummary(previous, current, integrity) {
|
|
3703
|
+
if (!previous) return current;
|
|
3704
|
+
const nextFacts = [...current.stable_facts];
|
|
3705
|
+
if (integrity.criticalFactLoss) {
|
|
3706
|
+
const existingKeys = new Set(nextFacts.map((fact) => fact.key));
|
|
3707
|
+
for (const fact of previous.stable_facts) {
|
|
3708
|
+
if (!existingKeys.has(fact.key) && !current.discarded_obsolete.includes(fact.key)) {
|
|
3709
|
+
nextFacts.push(fact);
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
const filteredFacts = integrity.obsoleteFactRevived ? nextFacts.filter((fact) => !current.discarded_obsolete.includes(fact.key)) : nextFacts;
|
|
3714
|
+
return { ...current, stable_facts: filteredFacts };
|
|
3715
|
+
}
|
|
2670
3716
|
function createContextSummarizeNode(opts) {
|
|
2671
3717
|
return async (state) => {
|
|
2672
|
-
const
|
|
2673
|
-
|
|
3718
|
+
const resolved = getResolvedSmartConfig(opts, state.agent);
|
|
3719
|
+
const config = resolved.summarization;
|
|
3720
|
+
if (!config.enable) return {};
|
|
2674
3721
|
const model = opts.model;
|
|
2675
3722
|
const messages = state.messages || [];
|
|
2676
3723
|
const onEvent = state.ctx?.__onEvent;
|
|
2677
3724
|
const traceSession = state.ctx?.__traceSession;
|
|
2678
|
-
const compressableMessages = messages.filter(
|
|
3725
|
+
const compressableMessages = messages.filter(
|
|
3726
|
+
(m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
|
|
3727
|
+
);
|
|
2679
3728
|
if (compressableMessages.length === 0) {
|
|
2680
3729
|
return {};
|
|
2681
3730
|
}
|
|
@@ -2687,6 +3736,9 @@ function createContextSummarizeNode(opts) {
|
|
|
2687
3736
|
let accumulatedTokens = 0;
|
|
2688
3737
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2689
3738
|
const m = messages[i];
|
|
3739
|
+
if (isSyntheticSummaryToolMessage(m) || isSyntheticSummaryAssistantMessage(m)) {
|
|
3740
|
+
continue;
|
|
3741
|
+
}
|
|
2690
3742
|
let content = "";
|
|
2691
3743
|
if (typeof m.content === "string") content = m.content;
|
|
2692
3744
|
else if (Array.isArray(m.content)) {
|
|
@@ -2698,39 +3750,60 @@ function createContextSummarizeNode(opts) {
|
|
|
2698
3750
|
}
|
|
2699
3751
|
let prefix = `${m.role.toUpperCase()}`;
|
|
2700
3752
|
if (m.name) prefix += ` (${m.name})`;
|
|
2701
|
-
const
|
|
2702
|
-
const messageTokens = countApproxTokens(
|
|
3753
|
+
const messageText2 = `${prefix}: ${content}`;
|
|
3754
|
+
const messageTokens = countApproxTokens(messageText2);
|
|
2703
3755
|
if (accumulatedTokens + messageTokens > availableTokensForConversation) {
|
|
2704
3756
|
messageTexts.unshift("[Earlier conversation history omitted for brevity]");
|
|
2705
3757
|
break;
|
|
2706
3758
|
}
|
|
2707
|
-
messageTexts.unshift(
|
|
3759
|
+
messageTexts.unshift(messageText2);
|
|
2708
3760
|
accumulatedTokens += messageTokens;
|
|
2709
3761
|
}
|
|
2710
3762
|
const conversationText = messageTexts.join("\n\n");
|
|
3763
|
+
const canonicalFacts = extractCanonicalFacts(compressableMessages);
|
|
2711
3764
|
const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
|
|
2712
|
-
const
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
3765
|
+
const previousStructuredSummary = Array.isArray(state.summaryRecords) && state.summaryRecords.length > 0 ? state.summaryRecords[state.summaryRecords.length - 1] : void 0;
|
|
3766
|
+
const defaultPrompt = `Summarize the conversation into strict JSON.
|
|
3767
|
+
|
|
3768
|
+
Return exactly one JSON object with this schema:
|
|
3769
|
+
{
|
|
3770
|
+
"stable_facts": [{ "key": string, "value": string, "confidence": number }],
|
|
3771
|
+
"active_goals": [string],
|
|
3772
|
+
"open_questions": [string],
|
|
3773
|
+
"discarded_obsolete": [string],
|
|
3774
|
+
"rawSummary": string
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
Rules:
|
|
3778
|
+
- Keep stable_facts only for facts that future turns must remember.
|
|
3779
|
+
- Put invalidated or superseded fact keys in discarded_obsolete.
|
|
3780
|
+
- Preserve active_goals still relevant to the user.
|
|
3781
|
+
- Preserve unresolved questions.
|
|
3782
|
+
- Do not include prose outside JSON.
|
|
3783
|
+
- Target compression ratio: ${config.summaryCompressionRatioTarget}.
|
|
3784
|
+
- Summary mode: ${config.summaryMode}.
|
|
2718
3785
|
|
|
2719
|
-
Previous summary
|
|
3786
|
+
Previous summary text:
|
|
2720
3787
|
${previousSummary || "(none)"}
|
|
2721
3788
|
|
|
3789
|
+
Previous structured summary:
|
|
3790
|
+
${previousStructuredSummary ? JSON.stringify(previousStructuredSummary) : "(none)"}
|
|
3791
|
+
|
|
2722
3792
|
Conversation:
|
|
2723
3793
|
${conversationText}
|
|
2724
3794
|
|
|
2725
|
-
|
|
3795
|
+
Canonical facts extracted from tool outputs:
|
|
3796
|
+
${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fact.value}`).join("\n") : "(none)"}`;
|
|
2726
3797
|
const template = config.promptTemplate || defaultPrompt;
|
|
2727
3798
|
const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
|
|
2728
3799
|
const summaryPrompt = [
|
|
2729
|
-
systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
|
|
3800
|
+
systemMessage("You are a helpful assistant that summarizes conversation history efficiently. Return strict JSON only."),
|
|
2730
3801
|
humanMessage(promptBody)
|
|
2731
3802
|
];
|
|
2732
3803
|
const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
|
|
2733
3804
|
let summaryText = "Summary unavailable.";
|
|
3805
|
+
let structuredSummary;
|
|
3806
|
+
let integrity;
|
|
2734
3807
|
let outputTokensEstimate = 0;
|
|
2735
3808
|
let durationMs = 0;
|
|
2736
3809
|
let inputTokensActual;
|
|
@@ -2739,10 +3812,23 @@ Summary:`;
|
|
|
2739
3812
|
let totalTokensActual;
|
|
2740
3813
|
const startTime = Date.now();
|
|
2741
3814
|
try {
|
|
3815
|
+
if (!model || typeof model.invoke !== "function") {
|
|
3816
|
+
throw new Error("Summarization model is unavailable.");
|
|
3817
|
+
}
|
|
2742
3818
|
const response = await model.invoke(summaryPrompt);
|
|
2743
3819
|
durationMs = Date.now() - startTime;
|
|
2744
3820
|
summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
|
|
2745
3821
|
outputTokensEstimate = countApproxTokens(summaryText);
|
|
3822
|
+
const jsonText = extractJsonObject(summaryText);
|
|
3823
|
+
const parsed = jsonText ? JSON.parse(jsonText) : void 0;
|
|
3824
|
+
structuredSummary = normalizeStructuredSummary(parsed, summaryText);
|
|
3825
|
+
structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
|
|
3826
|
+
integrity = config.integrityCheck ? runIntegrityCheck(previousStructuredSummary, structuredSummary) : { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: [] };
|
|
3827
|
+
if (integrity && !integrity.passed) {
|
|
3828
|
+
structuredSummary = repairStructuredSummary(previousStructuredSummary, structuredSummary, integrity);
|
|
3829
|
+
integrity = { ...integrity, passed: true };
|
|
3830
|
+
}
|
|
3831
|
+
summaryText = renderStructuredSummary(structuredSummary);
|
|
2746
3832
|
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
|
|
2747
3833
|
const normalized = normalizeUsage(rawUsage);
|
|
2748
3834
|
if (normalized) {
|
|
@@ -2753,17 +3839,22 @@ Summary:`;
|
|
|
2753
3839
|
}
|
|
2754
3840
|
} catch (err) {
|
|
2755
3841
|
durationMs = Date.now() - startTime;
|
|
2756
|
-
|
|
3842
|
+
structuredSummary = normalizeStructuredSummary(void 0, previousSummary || conversationText.slice(0, 800));
|
|
3843
|
+
structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
|
|
3844
|
+
integrity = { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: ["Fallback summary generated locally."] };
|
|
3845
|
+
summaryText = renderStructuredSummary(structuredSummary);
|
|
2757
3846
|
const errorEvent = {
|
|
2758
3847
|
type: "summarization",
|
|
2759
|
-
summary:
|
|
2760
|
-
messagesCompressed:
|
|
3848
|
+
summary: summaryText,
|
|
3849
|
+
messagesCompressed: compressableMessages.length,
|
|
2761
3850
|
inputTokens: inputTokensEstimate,
|
|
2762
|
-
outputTokens:
|
|
3851
|
+
outputTokens: countApproxTokens(summaryText),
|
|
2763
3852
|
durationMs,
|
|
2764
3853
|
previousSummary: previousSummary || void 0,
|
|
2765
3854
|
tokenCountBefore,
|
|
2766
|
-
tokenCountAfter: tokenCountBefore
|
|
3855
|
+
tokenCountAfter: tokenCountBefore,
|
|
3856
|
+
structuredSummary,
|
|
3857
|
+
integrity
|
|
2767
3858
|
};
|
|
2768
3859
|
onEvent?.(errorEvent);
|
|
2769
3860
|
recordTraceEvent(traceSession, {
|
|
@@ -2773,11 +3864,10 @@ Summary:`;
|
|
|
2773
3864
|
status: "error",
|
|
2774
3865
|
durationMs,
|
|
2775
3866
|
inputTokens: inputTokensEstimate,
|
|
2776
|
-
outputTokens:
|
|
3867
|
+
outputTokens: countApproxTokens(summaryText),
|
|
2777
3868
|
error: { message: err?.message || String(err), stack: err?.stack },
|
|
2778
3869
|
messageList: sanitizeTracePayload(summaryPrompt)
|
|
2779
3870
|
});
|
|
2780
|
-
return {};
|
|
2781
3871
|
}
|
|
2782
3872
|
const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
|
|
2783
3873
|
messages.forEach((m, idx) => {
|
|
@@ -2791,6 +3881,9 @@ Summary:`;
|
|
|
2791
3881
|
});
|
|
2792
3882
|
const newMessages = messages.map((m) => {
|
|
2793
3883
|
if (m.role === "tool") {
|
|
3884
|
+
if (isSyntheticSummaryToolMessage(m)) {
|
|
3885
|
+
return m;
|
|
3886
|
+
}
|
|
2794
3887
|
const toolCallId = m.tool_call_id;
|
|
2795
3888
|
if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
|
|
2796
3889
|
return null;
|
|
@@ -2828,6 +3921,7 @@ Summary:`;
|
|
|
2828
3921
|
content: summaryText
|
|
2829
3922
|
};
|
|
2830
3923
|
const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
|
|
3924
|
+
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() }];
|
|
2831
3925
|
const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
|
|
2832
3926
|
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");
|
|
2833
3927
|
const tokenCountAfter = countApproxTokens(allTextAfter);
|
|
@@ -2843,8 +3937,10 @@ Summary:`;
|
|
|
2843
3937
|
previousSummary: previousSummary || void 0,
|
|
2844
3938
|
tokenCountBefore,
|
|
2845
3939
|
tokenCountAfter,
|
|
2846
|
-
archivedCount: compressableMessages.length
|
|
3940
|
+
archivedCount: compressableMessages.length,
|
|
2847
3941
|
// deprecated field for backward compatibility
|
|
3942
|
+
structuredSummary,
|
|
3943
|
+
integrity
|
|
2848
3944
|
};
|
|
2849
3945
|
onEvent?.(summarizationEvent);
|
|
2850
3946
|
recordTraceEvent(traceSession, {
|
|
@@ -2868,12 +3964,19 @@ Summary:`;
|
|
|
2868
3964
|
tokensSaved: tokenCountBefore - tokenCountAfter,
|
|
2869
3965
|
previousSummaryLength: previousSummary?.length || 0,
|
|
2870
3966
|
usageFromModel: inputTokensActual !== void 0,
|
|
2871
|
-
cachedInputTokens
|
|
3967
|
+
cachedInputTokens,
|
|
3968
|
+
integrity
|
|
2872
3969
|
}
|
|
2873
3970
|
});
|
|
2874
3971
|
return {
|
|
2875
3972
|
messages: finalMessages,
|
|
2876
|
-
summaries
|
|
3973
|
+
summaries,
|
|
3974
|
+
summaryRecords,
|
|
3975
|
+
watchdog: {
|
|
3976
|
+
...state.watchdog || {},
|
|
3977
|
+
compactions: (state.watchdog?.compactions || 0) + 1,
|
|
3978
|
+
lastAction: "summarized"
|
|
3979
|
+
}
|
|
2877
3980
|
};
|
|
2878
3981
|
};
|
|
2879
3982
|
}
|
|
@@ -2896,38 +3999,200 @@ async function applyTokenLimits({
|
|
|
2896
3999
|
}
|
|
2897
4000
|
return { ...state, messages: newMessages };
|
|
2898
4001
|
}
|
|
4002
|
+
var todoStatusSchema = z.enum(["not-started", "in-progress", "completed", "blocked"]);
|
|
4003
|
+
var todoWriteItemSchema = z.object({
|
|
4004
|
+
id: z.number().int().min(1).describe("Sequential id starting from 1"),
|
|
4005
|
+
step: z.string().min(1).optional(),
|
|
4006
|
+
title: z.string().min(1).optional(),
|
|
4007
|
+
description: z.string().min(1).optional(),
|
|
4008
|
+
owner: z.string().min(1).optional(),
|
|
4009
|
+
exitCriteria: z.string().min(1).optional(),
|
|
4010
|
+
status: todoStatusSchema,
|
|
4011
|
+
evidence: z.string().max(300).optional()
|
|
4012
|
+
});
|
|
4013
|
+
var todoUpdateItemSchema = z.object({
|
|
4014
|
+
id: z.number().int().min(1).describe("Existing todo id to update"),
|
|
4015
|
+
step: z.string().min(1).optional(),
|
|
4016
|
+
title: z.string().min(1).optional(),
|
|
4017
|
+
description: z.string().min(1).optional(),
|
|
4018
|
+
owner: z.string().min(1).optional(),
|
|
4019
|
+
exitCriteria: z.string().min(1).optional(),
|
|
4020
|
+
status: todoStatusSchema.optional(),
|
|
4021
|
+
evidence: z.string().max(300).optional()
|
|
4022
|
+
});
|
|
4023
|
+
function normalizeTodoItem(item, existing) {
|
|
4024
|
+
const id = item.id ?? existing?.id;
|
|
4025
|
+
const step = item.step || item.title || item.description || existing?.step || existing?.title || existing?.description || `Step ${id}`;
|
|
4026
|
+
const title = item.title || item.step || existing?.title || existing?.step || `Step ${id}`;
|
|
4027
|
+
const description = item.description || item.step || item.title || existing?.description || existing?.step || existing?.title || `Step ${id}`;
|
|
4028
|
+
return {
|
|
4029
|
+
...existing,
|
|
4030
|
+
...item,
|
|
4031
|
+
id,
|
|
4032
|
+
step,
|
|
4033
|
+
title,
|
|
4034
|
+
description,
|
|
4035
|
+
owner: item.owner || existing?.owner || "agent",
|
|
4036
|
+
exitCriteria: item.exitCriteria || existing?.exitCriteria || description || `Complete step ${id}`,
|
|
4037
|
+
status: item.status || existing?.status,
|
|
4038
|
+
evidence: item.evidence ?? existing?.evidence
|
|
4039
|
+
};
|
|
4040
|
+
}
|
|
4041
|
+
function validatePlanInvariants(todoList, requireSequentialIds) {
|
|
4042
|
+
const ids = todoList.map((item) => item.id);
|
|
4043
|
+
const uniqueIds = new Set(ids);
|
|
4044
|
+
if (uniqueIds.size !== ids.length) {
|
|
4045
|
+
return { ok: false, error: "Todo ids must be unique." };
|
|
4046
|
+
}
|
|
4047
|
+
if (requireSequentialIds) {
|
|
4048
|
+
const expectedIds = Array.from({ length: todoList.length }, (_, index) => index + 1);
|
|
4049
|
+
const isSequential = ids.every((id, index) => id === expectedIds[index]);
|
|
4050
|
+
if (!isSequential) {
|
|
4051
|
+
return { ok: false, error: "Todo ids must be sequential starting from 1 for write operations." };
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
const inProgressCount = todoList.filter((item) => item.status === "in-progress").length;
|
|
4055
|
+
if (inProgressCount > 1) {
|
|
4056
|
+
return { ok: false, error: "Only one todo item may be in-progress at a time." };
|
|
4057
|
+
}
|
|
4058
|
+
return { ok: true };
|
|
4059
|
+
}
|
|
4060
|
+
function calculateAdherenceScore(todoList) {
|
|
4061
|
+
const completedCount = todoList.filter((item) => item.status === "completed").length;
|
|
4062
|
+
return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
|
|
4063
|
+
}
|
|
2899
4064
|
function createContextTools(stateRef, opts) {
|
|
2900
4065
|
const tools = [];
|
|
2901
4066
|
if (opts?.planningEnabled) {
|
|
2902
4067
|
const manageTodo = createTool({
|
|
2903
4068
|
name: "manage_todo_list",
|
|
2904
|
-
description: "Manage a structured todo list to track progress and plan tasks throughout your coding session
|
|
4069
|
+
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",
|
|
2905
4070
|
schema: z.object({
|
|
2906
|
-
operation: z.enum(["write", "read"]),
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
title: z.string().min(1),
|
|
2910
|
-
description: z.string().min(1),
|
|
2911
|
-
status: z.enum(["not-started", "in-progress", "completed"]),
|
|
2912
|
-
evidence: z.string().max(200).optional()
|
|
2913
|
-
})).optional()
|
|
4071
|
+
operation: z.enum(["write", "read", "update"]),
|
|
4072
|
+
expectedVersion: z.number().int().min(0).optional(),
|
|
4073
|
+
todoList: z.array(z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
|
|
2914
4074
|
}),
|
|
2915
|
-
func: async ({ operation, todoList }) => {
|
|
4075
|
+
func: async ({ operation, expectedVersion, todoList }) => {
|
|
2916
4076
|
const onEvent = manageTodo._stateRef?.__onEvent;
|
|
4077
|
+
const currentVersion = stateRef.planVersion || 0;
|
|
2917
4078
|
if (operation === "read") {
|
|
2918
4079
|
const list = stateRef.todoList || [];
|
|
2919
|
-
onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list });
|
|
4080
|
+
onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
|
|
2920
4081
|
return list;
|
|
2921
4082
|
}
|
|
2922
|
-
if (
|
|
2923
|
-
|
|
4083
|
+
if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
|
|
4084
|
+
return {
|
|
4085
|
+
status: "error",
|
|
4086
|
+
operation,
|
|
4087
|
+
error: "Plan version mismatch. Read the latest plan and retry.",
|
|
4088
|
+
version: currentVersion,
|
|
4089
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4090
|
+
};
|
|
4091
|
+
}
|
|
4092
|
+
if (!Array.isArray(todoList)) {
|
|
4093
|
+
return {
|
|
4094
|
+
status: "error",
|
|
4095
|
+
operation,
|
|
4096
|
+
error: "todoList is required for write and update operations.",
|
|
4097
|
+
version: currentVersion,
|
|
4098
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4099
|
+
};
|
|
2924
4100
|
}
|
|
4101
|
+
if (operation === "write") {
|
|
4102
|
+
const parsed = z.array(todoWriteItemSchema).safeParse(todoList);
|
|
4103
|
+
if (!parsed.success) {
|
|
4104
|
+
return {
|
|
4105
|
+
status: "error",
|
|
4106
|
+
operation,
|
|
4107
|
+
error: parsed.error.issues[0]?.message || "Invalid todoList for write.",
|
|
4108
|
+
version: currentVersion,
|
|
4109
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4110
|
+
};
|
|
4111
|
+
}
|
|
4112
|
+
const normalizedList = parsed.data.map((item) => normalizeTodoItem(item));
|
|
4113
|
+
const validation = validatePlanInvariants(normalizedList, true);
|
|
4114
|
+
if (!validation.ok) {
|
|
4115
|
+
return {
|
|
4116
|
+
status: "error",
|
|
4117
|
+
operation,
|
|
4118
|
+
error: validation.error,
|
|
4119
|
+
version: currentVersion,
|
|
4120
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4121
|
+
};
|
|
4122
|
+
}
|
|
4123
|
+
stateRef.todoList = normalizedList;
|
|
4124
|
+
} else if (operation === "update") {
|
|
4125
|
+
const parsed = z.array(todoUpdateItemSchema).safeParse(todoList);
|
|
4126
|
+
if (!parsed.success) {
|
|
4127
|
+
return {
|
|
4128
|
+
status: "error",
|
|
4129
|
+
operation,
|
|
4130
|
+
error: parsed.error.issues[0]?.message || "Invalid todoList for update.",
|
|
4131
|
+
version: currentVersion,
|
|
4132
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4133
|
+
};
|
|
4134
|
+
}
|
|
4135
|
+
const currentList = Array.isArray(stateRef.todoList) ? stateRef.todoList : [];
|
|
4136
|
+
if (currentList.length === 0) {
|
|
4137
|
+
return {
|
|
4138
|
+
status: "error",
|
|
4139
|
+
operation,
|
|
4140
|
+
error: "No existing plan to update. Use write first.",
|
|
4141
|
+
version: currentVersion,
|
|
4142
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4143
|
+
};
|
|
4144
|
+
}
|
|
4145
|
+
const patchIds = /* @__PURE__ */ new Set();
|
|
4146
|
+
for (const item of parsed.data) {
|
|
4147
|
+
if (patchIds.has(item.id)) {
|
|
4148
|
+
return {
|
|
4149
|
+
status: "error",
|
|
4150
|
+
operation,
|
|
4151
|
+
error: "Update payload contains duplicate todo ids.",
|
|
4152
|
+
version: currentVersion,
|
|
4153
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4154
|
+
};
|
|
4155
|
+
}
|
|
4156
|
+
patchIds.add(item.id);
|
|
4157
|
+
}
|
|
4158
|
+
const currentMap = new Map(currentList.map((item) => [item.id, item]));
|
|
4159
|
+
for (const item of parsed.data) {
|
|
4160
|
+
if (!currentMap.has(item.id)) {
|
|
4161
|
+
return {
|
|
4162
|
+
status: "error",
|
|
4163
|
+
operation,
|
|
4164
|
+
error: `Cannot update missing todo id ${item.id}. Use write to replace the plan.`,
|
|
4165
|
+
version: currentVersion,
|
|
4166
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
const mergedList = currentList.map((item) => {
|
|
4171
|
+
const patch = parsed.data.find((candidate) => candidate.id === item.id);
|
|
4172
|
+
return patch ? normalizeTodoItem(patch, item) : item;
|
|
4173
|
+
});
|
|
4174
|
+
const validation = validatePlanInvariants(mergedList, false);
|
|
4175
|
+
if (!validation.ok) {
|
|
4176
|
+
return {
|
|
4177
|
+
status: "error",
|
|
4178
|
+
operation,
|
|
4179
|
+
error: validation.error,
|
|
4180
|
+
version: currentVersion,
|
|
4181
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4182
|
+
};
|
|
4183
|
+
}
|
|
4184
|
+
stateRef.todoList = mergedList;
|
|
4185
|
+
}
|
|
4186
|
+
stateRef.planVersion = currentVersion + 1;
|
|
4187
|
+
stateRef.adherenceScore = calculateAdherenceScore(stateRef.todoList || []);
|
|
2925
4188
|
const payload = {
|
|
2926
4189
|
status: "ok",
|
|
2927
4190
|
operation,
|
|
2928
|
-
count: Array.isArray(todoList) ? todoList.length : void 0
|
|
4191
|
+
count: Array.isArray(todoList) ? todoList.length : void 0,
|
|
4192
|
+
version: stateRef.planVersion || 1,
|
|
4193
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
2929
4194
|
};
|
|
2930
|
-
onEvent?.({ type: "plan", source: "manage_todo_list", operation:
|
|
4195
|
+
onEvent?.({ type: "plan", source: "manage_todo_list", operation, todoList: stateRef.todoList, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
|
|
2931
4196
|
return payload;
|
|
2932
4197
|
}
|
|
2933
4198
|
});
|
|
@@ -2936,12 +4201,13 @@ function createContextTools(stateRef, opts) {
|
|
|
2936
4201
|
}
|
|
2937
4202
|
const getTool = createTool({
|
|
2938
4203
|
name: "get_tool_response",
|
|
2939
|
-
description: "RETRIEVE tool execution response: Use this to access the full output of a tool execution that shows as 'SUMMARIZED' in the tool history.",
|
|
4204
|
+
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.",
|
|
2940
4205
|
schema: z.object({ executionId: z.string().describe("Tool execution id") }),
|
|
2941
4206
|
func: async ({ executionId }) => {
|
|
2942
|
-
|
|
4207
|
+
const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
|
|
4208
|
+
let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));
|
|
2943
4209
|
if (!execution) {
|
|
2944
|
-
execution = stateRef.toolHistoryArchived?.find((t) => t
|
|
4210
|
+
execution = stateRef.toolHistoryArchived?.find((t) => matchesExecution(t));
|
|
2945
4211
|
}
|
|
2946
4212
|
if (execution) {
|
|
2947
4213
|
return execution.rawOutput || execution.output;
|
|
@@ -2972,19 +4238,33 @@ function createContextTools(stateRef, opts) {
|
|
|
2972
4238
|
}
|
|
2973
4239
|
|
|
2974
4240
|
// src/graph/decisions.ts
|
|
4241
|
+
function isSyntheticSummaryMessage2(message) {
|
|
4242
|
+
if (!message) return false;
|
|
4243
|
+
if (message.role === "tool" && message.name === "summarize_context") {
|
|
4244
|
+
return true;
|
|
4245
|
+
}
|
|
4246
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
4247
|
+
return message.tool_calls.some((toolCall) => {
|
|
4248
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
4249
|
+
return toolName === "summarize_context";
|
|
4250
|
+
});
|
|
4251
|
+
}
|
|
4252
|
+
return false;
|
|
4253
|
+
}
|
|
2975
4254
|
function needsSummarization(state, opts, summarizationEnabled) {
|
|
2976
4255
|
if (!summarizationEnabled) {
|
|
2977
4256
|
return false;
|
|
2978
4257
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
maxTok = opts.summarization.maxTokens;
|
|
4258
|
+
if (state.ctx?.__needsSummarization) {
|
|
4259
|
+
return true;
|
|
2982
4260
|
}
|
|
2983
|
-
|
|
2984
|
-
|
|
4261
|
+
const resolved = getResolvedSmartConfig(opts, state.agent);
|
|
4262
|
+
if (!resolved.summarization.enable) {
|
|
4263
|
+
return false;
|
|
2985
4264
|
}
|
|
4265
|
+
const maxTok = resolved.summarization.summaryTriggerTokens;
|
|
2986
4266
|
try {
|
|
2987
|
-
const allText = (state.messages || []).map((m) => {
|
|
4267
|
+
const allText = (state.messages || []).filter((message) => !isSyntheticSummaryMessage2(message)).map((m) => {
|
|
2988
4268
|
if (typeof m.content === "string") return m.content;
|
|
2989
4269
|
if (Array.isArray(m.content)) {
|
|
2990
4270
|
return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
|
|
@@ -2995,7 +4275,8 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
2995
4275
|
return "";
|
|
2996
4276
|
}).join("\n");
|
|
2997
4277
|
const tokenCount = countApproxTokens(allText);
|
|
2998
|
-
const
|
|
4278
|
+
const contextRotScore = state.watchdog?.contextRotScore || 0;
|
|
4279
|
+
const needsSum = tokenCount > maxTok || resolved.watchdog.enabled && resolved.watchdog.autoCompaction && contextRotScore >= resolved.watchdog.contextRotThreshold;
|
|
2999
4280
|
if (process.env.DEBUG_SUMMARIZATION) {
|
|
3000
4281
|
const msgCount = (state.messages || []).length;
|
|
3001
4282
|
const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
|
|
@@ -3005,10 +4286,10 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
3005
4286
|
});
|
|
3006
4287
|
console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
|
|
3007
4288
|
toolDetails.forEach((d) => console.log(` ${d}`));
|
|
3008
|
-
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
|
|
4289
|
+
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Context rot: ${contextRotScore}, Needs summarization: ${needsSum}`);
|
|
3009
4290
|
}
|
|
3010
4291
|
return needsSum;
|
|
3011
|
-
} catch
|
|
4292
|
+
} catch {
|
|
3012
4293
|
return false;
|
|
3013
4294
|
}
|
|
3014
4295
|
}
|
|
@@ -3026,33 +4307,205 @@ function toolsDecisionFactory(opts, summarizationEnabled) {
|
|
|
3026
4307
|
};
|
|
3027
4308
|
}
|
|
3028
4309
|
|
|
4310
|
+
// src/smart/memory.ts
|
|
4311
|
+
function normalizeFact(scope, fact) {
|
|
4312
|
+
return {
|
|
4313
|
+
...fact,
|
|
4314
|
+
scope,
|
|
4315
|
+
obsolete: fact.obsolete ?? false,
|
|
4316
|
+
lastUpdatedAt: fact.lastUpdatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4317
|
+
confidence: Number.isFinite(fact.confidence) ? fact.confidence : 0.5
|
|
4318
|
+
};
|
|
4319
|
+
}
|
|
4320
|
+
var InMemoryMemoryStore = class {
|
|
4321
|
+
constructor() {
|
|
4322
|
+
this.buckets = /* @__PURE__ */ new Map();
|
|
4323
|
+
}
|
|
4324
|
+
async get(scope, options) {
|
|
4325
|
+
const includeObsolete = options?.includeObsolete === true;
|
|
4326
|
+
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());
|
|
4327
|
+
return typeof options?.limit === "number" ? all.slice(0, options.limit) : all;
|
|
4328
|
+
}
|
|
4329
|
+
async upsert(scope, facts) {
|
|
4330
|
+
const existing = [...this.buckets.get(scope) || []];
|
|
4331
|
+
for (const fact of facts) {
|
|
4332
|
+
const normalized = normalizeFact(scope, fact);
|
|
4333
|
+
const sameKey = existing.filter((entry) => entry.key === normalized.key && !entry.obsolete);
|
|
4334
|
+
for (const entry of sameKey) {
|
|
4335
|
+
if (entry.value !== normalized.value) {
|
|
4336
|
+
entry.obsolete = true;
|
|
4337
|
+
entry.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
const match = existing.find((entry) => entry.key === normalized.key && entry.value === normalized.value && !entry.obsolete);
|
|
4341
|
+
if (match) {
|
|
4342
|
+
match.confidence = Math.max(match.confidence, normalized.confidence);
|
|
4343
|
+
match.sourceTurn = normalized.sourceTurn;
|
|
4344
|
+
match.ttl = normalized.ttl;
|
|
4345
|
+
match.tags = normalized.tags;
|
|
4346
|
+
match.lastUpdatedAt = normalized.lastUpdatedAt;
|
|
4347
|
+
} else {
|
|
4348
|
+
existing.push(normalized);
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
this.buckets.set(scope, existing);
|
|
4352
|
+
}
|
|
4353
|
+
async markObsolete(scope, keys) {
|
|
4354
|
+
const bucket = this.buckets.get(scope) || [];
|
|
4355
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4356
|
+
for (const fact of bucket) {
|
|
4357
|
+
if (keys.includes(fact.key)) {
|
|
4358
|
+
fact.obsolete = true;
|
|
4359
|
+
fact.lastUpdatedAt = now;
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4362
|
+
this.buckets.set(scope, bucket);
|
|
4363
|
+
}
|
|
4364
|
+
async semanticSearch(scope, query, options) {
|
|
4365
|
+
const normalizedQuery = query.toLowerCase();
|
|
4366
|
+
const bucket = (this.buckets.get(scope) || []).filter((fact) => !fact.obsolete);
|
|
4367
|
+
const scored = bucket.map((fact) => {
|
|
4368
|
+
const haystack = `${fact.key} ${fact.value} ${(fact.tags || []).join(" ")}`.toLowerCase();
|
|
4369
|
+
const score = haystack.includes(normalizedQuery) ? 2 : normalizedQuery.split(/\s+/).filter(Boolean).reduce((acc, token) => acc + (haystack.includes(token) ? 1 : 0), 0);
|
|
4370
|
+
return { fact, score };
|
|
4371
|
+
}).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || right.fact.confidence - left.fact.confidence).map((entry) => entry.fact);
|
|
4372
|
+
return typeof options?.limit === "number" ? scored.slice(0, options.limit) : scored;
|
|
4373
|
+
}
|
|
4374
|
+
};
|
|
4375
|
+
function resolveMemoryStore(config) {
|
|
4376
|
+
if (config.memory.store) return config.memory.store;
|
|
4377
|
+
if (config.memory.provider === "inMemory") return new InMemoryMemoryStore();
|
|
4378
|
+
return void 0;
|
|
4379
|
+
}
|
|
4380
|
+
async function readMemoryFacts(store, config, query) {
|
|
4381
|
+
if (!store) return [];
|
|
4382
|
+
const scope = config.memory.scope;
|
|
4383
|
+
const limit = config.context.lastTurnsToKeep;
|
|
4384
|
+
const policy = config.memory.readPolicy;
|
|
4385
|
+
if (policy === "recent_only") {
|
|
4386
|
+
return store.get(scope, { limit });
|
|
4387
|
+
}
|
|
4388
|
+
if (policy === "semantic" && typeof store.semanticSearch === "function") {
|
|
4389
|
+
return store.semanticSearch(scope, query, { limit });
|
|
4390
|
+
}
|
|
4391
|
+
if (policy === "hybrid") {
|
|
4392
|
+
const recent = await store.get(scope, { limit: Math.max(3, Math.ceil(limit / 2)) });
|
|
4393
|
+
const semantic = typeof store.semanticSearch === "function" ? await store.semanticSearch(scope, query, { limit: Math.max(3, Math.ceil(limit / 2)) }) : [];
|
|
4394
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
4395
|
+
for (const fact of [...semantic, ...recent]) {
|
|
4396
|
+
deduped.set(`${fact.key}:${fact.value}`, fact);
|
|
4397
|
+
}
|
|
4398
|
+
return [...deduped.values()].slice(0, limit);
|
|
4399
|
+
}
|
|
4400
|
+
return store.get(scope, { limit });
|
|
4401
|
+
}
|
|
4402
|
+
async function writeSummaryFactsToMemory(store, config, summary, sourceTurn) {
|
|
4403
|
+
if (!store || !summary) return [];
|
|
4404
|
+
const facts = summary.stable_facts.map((fact) => ({
|
|
4405
|
+
key: fact.key,
|
|
4406
|
+
value: fact.value,
|
|
4407
|
+
sourceTurn,
|
|
4408
|
+
confidence: fact.confidence ?? 0.8,
|
|
4409
|
+
obsolete: false,
|
|
4410
|
+
tags: ["summary_fact"]
|
|
4411
|
+
}));
|
|
4412
|
+
if (summary.discarded_obsolete.length > 0) {
|
|
4413
|
+
await store.markObsolete(config.memory.scope, summary.discarded_obsolete);
|
|
4414
|
+
}
|
|
4415
|
+
if (facts.length > 0) {
|
|
4416
|
+
await store.upsert(config.memory.scope, facts);
|
|
4417
|
+
}
|
|
4418
|
+
return store.get(config.memory.scope, { limit: config.context.lastTurnsToKeep + summary.stable_facts.length });
|
|
4419
|
+
}
|
|
4420
|
+
function renderMemoryFacts(facts) {
|
|
4421
|
+
if (facts.length === 0) return "";
|
|
4422
|
+
return [
|
|
4423
|
+
"Memory facts:",
|
|
4424
|
+
...facts.map((fact) => `- ${fact.key}: ${fact.value} (confidence ${fact.confidence.toFixed(2)})`)
|
|
4425
|
+
].join("\n");
|
|
4426
|
+
}
|
|
4427
|
+
|
|
3029
4428
|
// src/smart/index.ts
|
|
3030
4429
|
function createSmartAgent(opts) {
|
|
3031
|
-
const
|
|
3032
|
-
const planningEnabled =
|
|
4430
|
+
const resolved = normalizeSmartAgentOptions(opts);
|
|
4431
|
+
const planningEnabled = resolved.planning.mode !== "off";
|
|
4432
|
+
const memoryStore = resolveMemoryStore(resolved);
|
|
4433
|
+
const runtimeOpts = {
|
|
4434
|
+
...opts,
|
|
4435
|
+
runtimeProfile: resolved.runtimeProfile,
|
|
4436
|
+
limits: resolved.limits,
|
|
4437
|
+
summarization: resolved.summarization,
|
|
4438
|
+
context: resolved.context,
|
|
4439
|
+
planning: resolved.planning,
|
|
4440
|
+
delegation: resolved.delegation,
|
|
4441
|
+
memory: { ...resolved.memory, store: memoryStore },
|
|
4442
|
+
toolResponses: resolved.toolResponses,
|
|
4443
|
+
watchdog: resolved.watchdog,
|
|
4444
|
+
useTodoList: planningEnabled
|
|
4445
|
+
};
|
|
4446
|
+
const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
|
|
3033
4447
|
const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
|
|
3034
4448
|
const mergedTools = [...opts.tools ?? [], ...contextTools];
|
|
3035
|
-
const base = createAgent({ ...
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
|
|
3043
|
-
const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
|
|
3044
|
-
const decideAfter = toolsDecisionFactory(opts, summarizationEnabled);
|
|
4449
|
+
const base = createAgent({ ...runtimeOpts, tools: mergedTools });
|
|
4450
|
+
base.__runtime.runtimeProfile = resolved.runtimeProfile;
|
|
4451
|
+
base.__runtime.smart = resolved;
|
|
4452
|
+
const summarizationEnabled = resolved.summarization.enable !== false;
|
|
4453
|
+
const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
|
|
4454
|
+
const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
|
|
4455
|
+
const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
|
|
3045
4456
|
const structuredOutputHint = opts.outputSchema ? [
|
|
3046
4457
|
"A structured output schema is active.",
|
|
3047
4458
|
"Do NOT output the final JSON directly as an assistant message.",
|
|
3048
4459
|
"When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
|
|
3049
4460
|
"Call it exactly once then STOP producing further assistant messages."
|
|
3050
4461
|
].join("\n") : "";
|
|
4462
|
+
const runtimeHint = [
|
|
4463
|
+
resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
|
|
4464
|
+
`Context policy: ${resolved.context.policy}.`,
|
|
4465
|
+
`Planning mode: ${resolved.planning.mode}. Replan policy: ${resolved.planning.replanPolicy}.`,
|
|
4466
|
+
`Delegation mode: ${resolved.delegation.mode}. Child context policy: ${resolved.delegation.childContextPolicy}.`,
|
|
4467
|
+
`Tool response policy: ${resolved.context.toolResponsePolicy}.`
|
|
4468
|
+
].join("\n");
|
|
4469
|
+
function latestUserPrompt(messages) {
|
|
4470
|
+
const latest = [...messages].reverse().find((message) => message.role === "user");
|
|
4471
|
+
if (!latest) return "";
|
|
4472
|
+
return typeof latest.content === "string" ? latest.content : Array.isArray(latest.content) ? latest.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join(" ") : "";
|
|
4473
|
+
}
|
|
4474
|
+
async function syncMemory(state) {
|
|
4475
|
+
const query = latestUserPrompt(state.messages || []);
|
|
4476
|
+
const memoryFacts = await readMemoryFacts(memoryStore, resolved, query);
|
|
4477
|
+
return { ...state, memoryFacts };
|
|
4478
|
+
}
|
|
4479
|
+
async function persistLatestSummary(state) {
|
|
4480
|
+
if (!memoryStore) return state;
|
|
4481
|
+
if (resolved.memory.writePolicy === "manual") return state;
|
|
4482
|
+
const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
|
|
4483
|
+
if (!latestSummary) return state;
|
|
4484
|
+
if (resolved.memory.writePolicy === "auto_important" && latestSummary.stable_facts.length === 0) {
|
|
4485
|
+
return state;
|
|
4486
|
+
}
|
|
4487
|
+
const memoryFacts = await writeSummaryFactsToMemory(memoryStore, resolved, latestSummary, Math.max(1, (state.messages || []).filter((message) => message.role === "user").length));
|
|
4488
|
+
return { ...state, memoryFacts };
|
|
4489
|
+
}
|
|
4490
|
+
function syncPlanState(state) {
|
|
4491
|
+
if (!Array.isArray(stateRef.todoList)) return state;
|
|
4492
|
+
return {
|
|
4493
|
+
...state,
|
|
4494
|
+
plan: {
|
|
4495
|
+
version: stateRef.planVersion || state.planVersion || 1,
|
|
4496
|
+
steps: stateRef.todoList,
|
|
4497
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4498
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4499
|
+
},
|
|
4500
|
+
planVersion: stateRef.planVersion || state.planVersion || 1
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
3051
4503
|
function systemMessage2() {
|
|
3052
4504
|
const sys = buildSystemPrompt(
|
|
3053
|
-
[opts.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
|
|
3054
|
-
|
|
3055
|
-
opts.name || "Agent"
|
|
4505
|
+
[opts.systemPrompt, runtimeHint, structuredOutputHint].filter(Boolean).join("\n"),
|
|
4506
|
+
planningEnabled,
|
|
4507
|
+
opts.name || "Agent",
|
|
4508
|
+
opts.todoListPrompt
|
|
3056
4509
|
);
|
|
3057
4510
|
return { role: "system", content: sys };
|
|
3058
4511
|
}
|
|
@@ -3060,37 +4513,81 @@ function createSmartAgent(opts) {
|
|
|
3060
4513
|
invoke: async (input, config) => {
|
|
3061
4514
|
stateRef.toolHistory = input.toolHistory;
|
|
3062
4515
|
stateRef.toolHistoryArchived = input.toolHistoryArchived;
|
|
4516
|
+
stateRef.todoList = input.plan?.steps;
|
|
4517
|
+
stateRef.planVersion = input.planVersion || input.plan?.version || 0;
|
|
4518
|
+
stateRef.adherenceScore = input.plan?.adherenceScore || 0;
|
|
3063
4519
|
const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
|
|
3064
4520
|
const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
|
|
3065
|
-
let state = { ...input, messages: seedMessages };
|
|
4521
|
+
let state = await syncMemory({ ...input, messages: seedMessages });
|
|
3066
4522
|
let lastResult = null;
|
|
3067
|
-
|
|
4523
|
+
let rawMessages = [...seedMessages];
|
|
4524
|
+
const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
|
|
3068
4525
|
const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
|
|
4526
|
+
let previousTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
|
|
3069
4527
|
for (let i = 0; i < iterationLimit; i++) {
|
|
4528
|
+
state = { ...state, messages: rawMessages };
|
|
3070
4529
|
const next = summarizationEnabled ? decideBefore(state) : "agent";
|
|
3071
4530
|
if (next === "contextSummarize" && summarizer) {
|
|
3072
4531
|
const delta = await summarizer(state);
|
|
3073
4532
|
if (!delta || Object.keys(delta).length === 0) ; else {
|
|
3074
|
-
state = { ...state, ...delta };
|
|
4533
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4534
|
+
rawMessages = [...state.messages || rawMessages];
|
|
3075
4535
|
continue;
|
|
3076
4536
|
}
|
|
3077
4537
|
}
|
|
3078
|
-
const
|
|
4538
|
+
const modelMessages = buildModelMessages(state, resolved);
|
|
4539
|
+
const res = await base.invoke({ ...state, messages: modelMessages }, config);
|
|
3079
4540
|
lastResult = res;
|
|
3080
4541
|
const currentSummaries = state.summaries;
|
|
4542
|
+
const currentSummaryRecords = state.summaryRecords;
|
|
4543
|
+
const currentPlan = state.plan;
|
|
4544
|
+
const appendedMessages = res.messages.slice(modelMessages.length);
|
|
4545
|
+
rawMessages = [...rawMessages, ...appendedMessages];
|
|
3081
4546
|
state = res.state || { ...state, messages: res.messages };
|
|
4547
|
+
state = { ...state, messages: rawMessages };
|
|
3082
4548
|
if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
|
|
3083
4549
|
state = { ...state, summaries: currentSummaries };
|
|
3084
4550
|
}
|
|
4551
|
+
if (currentSummaryRecords && currentSummaryRecords.length > 0 && (!state.summaryRecords || state.summaryRecords.length === 0)) {
|
|
4552
|
+
state = { ...state, summaryRecords: currentSummaryRecords };
|
|
4553
|
+
}
|
|
4554
|
+
if (currentPlan && !state.plan) {
|
|
4555
|
+
state = { ...state, plan: currentPlan };
|
|
4556
|
+
}
|
|
4557
|
+
state = syncPlanState(await syncMemory(state));
|
|
4558
|
+
stateRef.toolHistory = state.toolHistory;
|
|
4559
|
+
stateRef.toolHistoryArchived = state.toolHistoryArchived;
|
|
4560
|
+
const currentTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
|
|
4561
|
+
const tokenDrift = Math.max(0, currentTokenCount - previousTokenCount);
|
|
4562
|
+
const toolMessagesThisTurn = appendedMessages.filter((message) => message.role === "tool").length;
|
|
4563
|
+
const overToolingRate = appendedMessages.length === 0 ? 0 : Number((toolMessagesThisTurn / appendedMessages.length).toFixed(2));
|
|
4564
|
+
state = {
|
|
4565
|
+
...state,
|
|
4566
|
+
watchdog: {
|
|
4567
|
+
tokenDrift,
|
|
4568
|
+
contextRotScore: estimateContextRotScore(state),
|
|
4569
|
+
overToolingRate,
|
|
4570
|
+
compactions: state.watchdog?.compactions || 0,
|
|
4571
|
+
lastAction: appendedMessages.length > 0 ? "agent_turn" : state.watchdog?.lastAction
|
|
4572
|
+
}
|
|
4573
|
+
};
|
|
4574
|
+
previousTokenCount = currentTokenCount;
|
|
4575
|
+
if (resolved.watchdog.enabled && resolved.watchdog.autoCompaction) {
|
|
4576
|
+
const shouldCompact = tokenDrift >= resolved.watchdog.tokenDriftThreshold || (state.watchdog?.overToolingRate || 0) >= resolved.watchdog.overToolingSpikeThreshold || (state.watchdog?.contextRotScore || 0) >= resolved.watchdog.contextRotThreshold;
|
|
4577
|
+
if (shouldCompact) {
|
|
4578
|
+
state = { ...state, ctx: { ...state.ctx || {}, __needsSummarization: true } };
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
3085
4581
|
if (state.ctx?.__needsSummarization && summarizer) {
|
|
3086
4582
|
const ctx = { ...state.ctx || {} };
|
|
3087
4583
|
delete ctx.__needsSummarization;
|
|
3088
4584
|
state = { ...state, ctx };
|
|
3089
4585
|
const delta = await summarizer(state);
|
|
3090
4586
|
if (delta && Object.keys(delta).length > 0) {
|
|
3091
|
-
state = { ...state, ...delta };
|
|
4587
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4588
|
+
rawMessages = [...state.messages || rawMessages];
|
|
4589
|
+
continue;
|
|
3092
4590
|
}
|
|
3093
|
-
continue;
|
|
3094
4591
|
}
|
|
3095
4592
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
3096
4593
|
if (summarizationEnabled) {
|
|
@@ -3098,7 +4595,8 @@ function createSmartAgent(opts) {
|
|
|
3098
4595
|
if (after === "contextSummarize" && summarizer) {
|
|
3099
4596
|
const delta = await summarizer(state);
|
|
3100
4597
|
if (delta && Object.keys(delta).length > 0) {
|
|
3101
|
-
state = { ...state, ...delta };
|
|
4598
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4599
|
+
rawMessages = [...state.messages || rawMessages];
|
|
3102
4600
|
continue;
|
|
3103
4601
|
}
|
|
3104
4602
|
}
|
|
@@ -3111,10 +4609,12 @@ function createSmartAgent(opts) {
|
|
|
3111
4609
|
}
|
|
3112
4610
|
if (state.summaries && state.summaries.length > 0) {
|
|
3113
4611
|
if (lastResult.state) {
|
|
3114
|
-
lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
|
|
4612
|
+
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 } };
|
|
3115
4613
|
} else {
|
|
3116
|
-
lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
|
|
4614
|
+
lastResult = { ...lastResult, state: { ...state, summaries: state.summaries, summaryRecords: state.summaryRecords, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
|
|
3117
4615
|
}
|
|
4616
|
+
} else if (lastResult.state) {
|
|
4617
|
+
lastResult = { ...lastResult, state: { ...lastResult.state, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
|
|
3118
4618
|
}
|
|
3119
4619
|
return lastResult;
|
|
3120
4620
|
},
|
|
@@ -3128,6 +4628,141 @@ function createSmartAgent(opts) {
|
|
|
3128
4628
|
return instance;
|
|
3129
4629
|
}
|
|
3130
4630
|
|
|
4631
|
+
// src/smart/eval.ts
|
|
4632
|
+
function normalizeProfileTarget(target) {
|
|
4633
|
+
if (typeof target === "string") {
|
|
4634
|
+
return {
|
|
4635
|
+
label: target,
|
|
4636
|
+
runtimeProfile: target,
|
|
4637
|
+
baseProfile: target === "custom" ? "balanced" : target
|
|
4638
|
+
};
|
|
4639
|
+
}
|
|
4640
|
+
return {
|
|
4641
|
+
label: target.label,
|
|
4642
|
+
runtimeProfile: target.runtimeProfile,
|
|
4643
|
+
baseProfile: target.baseProfile ?? (target.runtimeProfile === "custom" ? target.customProfile?.extends ?? "balanced" : target.runtimeProfile),
|
|
4644
|
+
customProfile: target.customProfile
|
|
4645
|
+
};
|
|
4646
|
+
}
|
|
4647
|
+
function normalizeText(value) {
|
|
4648
|
+
return (value || "").toLowerCase();
|
|
4649
|
+
}
|
|
4650
|
+
function ratio(numerator, denominator) {
|
|
4651
|
+
if (denominator <= 0) return 1;
|
|
4652
|
+
return Number((numerator / denominator).toFixed(3));
|
|
4653
|
+
}
|
|
4654
|
+
function sum(values) {
|
|
4655
|
+
return values.reduce((acc, value) => acc + value, 0);
|
|
4656
|
+
}
|
|
4657
|
+
function mean(values) {
|
|
4658
|
+
if (values.length === 0) return 0;
|
|
4659
|
+
return Number((sum(values) / values.length).toFixed(3));
|
|
4660
|
+
}
|
|
4661
|
+
function extractTotalTokens(usage) {
|
|
4662
|
+
if (!usage?.totals || typeof usage.totals !== "object") return 0;
|
|
4663
|
+
return Object.values(usage.totals).reduce((acc, entry) => acc + (Number(entry?.total) || 0), 0);
|
|
4664
|
+
}
|
|
4665
|
+
function caseResultFromRun(descriptor, testCase, content, events, state, latencyMs, totalTokens) {
|
|
4666
|
+
const normalizedContent = normalizeText(content);
|
|
4667
|
+
const matchedPhrases = (testCase.expectedPhrases || []).filter((phrase) => normalizedContent.includes(normalizeText(phrase)));
|
|
4668
|
+
const matchedFacts = (testCase.expectedFacts || []).filter((fact) => {
|
|
4669
|
+
const memoryMatch = state?.memoryFacts?.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value)));
|
|
4670
|
+
const summaryMatch = state?.summaryRecords?.some((record) => record.stable_facts.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value))));
|
|
4671
|
+
return Boolean(memoryMatch || summaryMatch || normalizedContent.includes(normalizeText(fact.value)));
|
|
4672
|
+
});
|
|
4673
|
+
const forbiddenMisses = (testCase.forbiddenPhrases || []).filter((phrase) => !normalizedContent.includes(normalizeText(phrase)));
|
|
4674
|
+
const toolCalls = events.filter((event) => event.type === "tool_call" && event.phase === "success");
|
|
4675
|
+
const expectedToolHits = (testCase.expectedToolNames || []).filter((toolName) => toolCalls.some((event) => event.name === toolName));
|
|
4676
|
+
const toolPrecision = ratio(expectedToolHits.length, Math.max(toolCalls.length, 1));
|
|
4677
|
+
const recallAccuracy = ratio(matchedPhrases.length + matchedFacts.length, (testCase.expectedPhrases?.length || 0) + (testCase.expectedFacts?.length || 0));
|
|
4678
|
+
const obsoleteDropAccuracy = ratio(forbiddenMisses.length, testCase.forbiddenPhrases?.length || 0);
|
|
4679
|
+
const trajectoryScore = ratio(expectedToolHits.length, testCase.expectedToolNames?.length || 0);
|
|
4680
|
+
const hadRecoverableError = events.some((event) => event.type === "tool_call" && event.phase === "error");
|
|
4681
|
+
const recoveryRate = hadRecoverableError ? Number((normalizedContent.length > 0 ? 1 : 0).toFixed(3)) : 1;
|
|
4682
|
+
const overToolingRate = toolCalls.length === 0 ? 0 : Number((Math.max(0, toolCalls.length - Math.max(testCase.expectedToolNames?.length || 0, 1)) / toolCalls.length).toFixed(3));
|
|
4683
|
+
const success = recallAccuracy >= 0.8 && obsoleteDropAccuracy >= 0.8;
|
|
4684
|
+
return {
|
|
4685
|
+
id: testCase.id,
|
|
4686
|
+
family: testCase.family,
|
|
4687
|
+
success,
|
|
4688
|
+
recallAccuracy,
|
|
4689
|
+
obsoleteDropAccuracy,
|
|
4690
|
+
trajectoryScore: Number(((trajectoryScore + toolPrecision) / 2).toFixed(3)),
|
|
4691
|
+
recoveryRate,
|
|
4692
|
+
overToolingRate,
|
|
4693
|
+
latencyMs,
|
|
4694
|
+
totalTokens,
|
|
4695
|
+
profile: descriptor.runtimeProfile,
|
|
4696
|
+
profileLabel: descriptor.label,
|
|
4697
|
+
baseProfile: descriptor.baseProfile,
|
|
4698
|
+
notes: success ? [] : [
|
|
4699
|
+
recallAccuracy < 0.8 ? "Recall below threshold." : "",
|
|
4700
|
+
obsoleteDropAccuracy < 0.8 ? "Obsolete filtering below threshold." : "",
|
|
4701
|
+
trajectoryScore < 0.5 ? "Tool trajectory diverged from expectation." : ""
|
|
4702
|
+
].filter(Boolean)
|
|
4703
|
+
};
|
|
4704
|
+
}
|
|
4705
|
+
function aggregateMetrics(caseResults) {
|
|
4706
|
+
const taskSuccess = ratio(caseResults.filter((result) => result.success).length, caseResults.length);
|
|
4707
|
+
const recallAccuracy = mean(caseResults.map((result) => result.recallAccuracy));
|
|
4708
|
+
const obsoleteDropAccuracy = mean(caseResults.map((result) => result.obsoleteDropAccuracy));
|
|
4709
|
+
const trajectoryScore = mean(caseResults.map((result) => result.trajectoryScore));
|
|
4710
|
+
const recoveryRate = mean(caseResults.map((result) => result.recoveryRate));
|
|
4711
|
+
const overToolingRate = mean(caseResults.map((result) => result.overToolingRate));
|
|
4712
|
+
const latencyMs = mean(caseResults.map((result) => result.latencyMs));
|
|
4713
|
+
const totalTokens = sum(caseResults.map((result) => result.totalTokens || 0));
|
|
4714
|
+
const toolPrecision = trajectoryScore;
|
|
4715
|
+
const costScore = totalTokens <= 0 ? 1 : Number((1 / (1 + totalTokens / 1e4)).toFixed(3));
|
|
4716
|
+
const score = Number((0.45 * taskSuccess + 0.2 * trajectoryScore + 0.15 * toolPrecision + 0.1 * recoveryRate + 0.1 * costScore).toFixed(3));
|
|
4717
|
+
return {
|
|
4718
|
+
taskSuccess,
|
|
4719
|
+
recallAccuracy,
|
|
4720
|
+
obsoleteDropAccuracy,
|
|
4721
|
+
trajectoryScore,
|
|
4722
|
+
recoveryRate,
|
|
4723
|
+
overToolingRate,
|
|
4724
|
+
latencyMs,
|
|
4725
|
+
totalTokens,
|
|
4726
|
+
score
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4729
|
+
async function runSmartAgentEvalHarness(params) {
|
|
4730
|
+
const results = [];
|
|
4731
|
+
for (const profileTarget of params.profiles) {
|
|
4732
|
+
const descriptor = normalizeProfileTarget(profileTarget);
|
|
4733
|
+
const caseResults = [];
|
|
4734
|
+
for (const testCase of params.cases) {
|
|
4735
|
+
const agent = params.createAgent(descriptor.runtimeProfile, descriptor);
|
|
4736
|
+
const events = [];
|
|
4737
|
+
const startedAt = Date.now();
|
|
4738
|
+
const result = await agent.invoke({
|
|
4739
|
+
messages: [{ role: "user", content: testCase.prompt }]
|
|
4740
|
+
}, {
|
|
4741
|
+
onEvent: (event) => {
|
|
4742
|
+
events.push(event);
|
|
4743
|
+
}
|
|
4744
|
+
});
|
|
4745
|
+
caseResults.push(caseResultFromRun(
|
|
4746
|
+
descriptor,
|
|
4747
|
+
testCase,
|
|
4748
|
+
result.content,
|
|
4749
|
+
events,
|
|
4750
|
+
result.state,
|
|
4751
|
+
Date.now() - startedAt,
|
|
4752
|
+
extractTotalTokens(result.metadata?.usage)
|
|
4753
|
+
));
|
|
4754
|
+
}
|
|
4755
|
+
results.push({
|
|
4756
|
+
profile: descriptor.runtimeProfile,
|
|
4757
|
+
profileLabel: descriptor.label,
|
|
4758
|
+
baseProfile: descriptor.baseProfile,
|
|
4759
|
+
metrics: aggregateMetrics(caseResults),
|
|
4760
|
+
cases: caseResults
|
|
4761
|
+
});
|
|
4762
|
+
}
|
|
4763
|
+
return results;
|
|
4764
|
+
}
|
|
4765
|
+
|
|
3131
4766
|
// src/utils/content.ts
|
|
3132
4767
|
function contentToString(content) {
|
|
3133
4768
|
try {
|
|
@@ -3405,6 +5040,6 @@ function createCodeGuardrail(options) {
|
|
|
3405
5040
|
});
|
|
3406
5041
|
}
|
|
3407
5042
|
|
|
3408
|
-
export { GuardrailPhase, agentVerdictRule, applyTokenLimits, buildSystemPrompt, captureSnapshot, codePresenceRule, cognipeerSink, countApproxTokens, countMessagesTokens, createAgent, createAgentNode, createCodeGuardrail, createContextSummarizeNode, createContextTools, createGuardrail, createJsonGuardrail, createRegexGuardrail, createResolverNode, createSmartAgent, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customSink, evaluateGuardrails, fileSink, fromLangchainModel, fromLangchainTools, httpSink, isSmartModel, jsonSchemaRule, regexRule, resolveToolApprovalState, restoreSnapshot, startStreamingSession, withTools };
|
|
5043
|
+
export { DEFAULT_PROFILE_CONFIGS, GuardrailPhase, InMemoryMemoryStore, agentVerdictRule, applyTokenLimits, buildSystemPrompt, captureSnapshot, codePresenceRule, cognipeerSink, countApproxTokens, countMessagesTokens, createAgent, createAgentNode, createCodeGuardrail, createContextSummarizeNode, createContextTools, createGuardrail, createJsonGuardrail, createRegexGuardrail, createResolverNode, createSmartAgent, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customSink, evaluateGuardrails, fileSink, fromLangchainModel, fromLangchainTools, generateSpanId, generateTraceId, getResolvedSmartConfig, httpSink, isSmartModel, jsonSchemaRule, normalizeSmartAgentOptions, otlpSink, readMemoryFacts, regexRule, renderMemoryFacts, resolveMemoryStore, resolveToolApprovalState, restoreSnapshot, runSmartAgentEvalHarness, startStreamingSession, traceSessionToOtlp, withTools, writeSummaryFactsToMemory };
|
|
3409
5044
|
//# sourceMappingURL=index.mjs.map
|
|
3410
5045
|
//# sourceMappingURL=index.mjs.map
|